15 minlesson

Parcel Registration Workflow

Parcel Registration Workflow

Parcel registration is the entry point of any tracking system. When a shipper wants to send a package, the API must capture all relevant details, generate a unique tracking number, and create the first tracking event. This lesson covers the end-to-end workflow and the design decisions behind it.

What Happens When a Parcel Is Registered?

Registration is more than just inserting a row into a database. It involves several coordinated steps:

  1. Validate the request -- ensure all required fields are present and well-formed
  2. Verify referenced entities -- confirm the shipper and recipient addresses exist
  3. Generate a tracking number -- create a unique, human-readable identifier
  4. Calculate derived fields -- set estimated delivery date based on service type
  5. Set the initial status -- every parcel starts in LabelCreated
  6. Create the first tracking event -- record "Label created, shipment information sent to carrier"
  7. Persist everything -- save the parcel and tracking event atomically
  8. Return the result -- send back the full parcel with its new tracking number

Each step has design implications. Let's walk through the key ones.

Tracking Number Generation

A tracking number is the public identifier for a parcel. It must be:

  • Unique -- no two parcels should ever share a tracking number
  • Human-readable -- customers type these into search fields
  • Non-sequential -- sequential IDs leak business information (volume, order of registration)

The PKT Format

In this API, tracking numbers follow the format PKT followed by 12 random alphanumeric characters:

1PKT7G4KM2XB9NQ1
2PKTW3RF8JL5YA0H
3PKT9C6DV1TP4ZE2

The PKT prefix makes it immediately recognizable as a parcel tracking number. The 12-character random portion provides a keyspace of 36^12 (over 4.7 quadrillion combinations), which is more than sufficient to avoid collisions in practice.

Generation Strategies

There are several approaches to generating unique identifiers:

StrategyProsCons
Random alphanumericSimple, non-sequentialCollision possible (but extremely unlikely)
GUIDsGuaranteed uniqueLong, not human-friendly
Snowflake IDsOrdered, unique across nodesComplex to implement
Database sequencesSimple, guaranteed uniqueSequential, leaks info

For this API, random alphanumeric with a prefix is the best balance of simplicity and usability. The probability of a collision with 12 alphanumeric characters is negligible for any realistic parcel volume.

Collision Handling

Even though collisions are astronomically unlikely, a production system should handle them gracefully. The simplest approach is to check if a generated tracking number already exists and regenerate if it does. With a unique constraint on the database column, the worst case is a caught exception followed by a retry.

Initial Status and the Parcel Lifecycle

Every parcel begins its life in the LabelCreated status. This status means the shipper has created a shipping label but the carrier hasn't physically received the package yet.

The typical parcel lifecycle looks like this:

LabelCreated --> PickedUp --> InTransit --> OutForDelivery --> Delivered

There are also exception statuses like AttemptFailed, Held, and Returned, but those come later in the course. At registration time, only LabelCreated is relevant.

The Initial Tracking Event

When you register a parcel, the system automatically creates the first entry in the tracking history:

  • Status: LabelCreated
  • Description: "Label created, shipment information sent to carrier"
  • Timestamp: The current UTC time
  • Location: Typically the shipper's address or null

This mirrors how real carriers work. When you create a shipping label on FedEx or UPS, the first event in the tracking history is always the label creation.

Estimated Delivery Calculation

The estimated delivery date is a derived field based on the selected service type. Different service levels have different transit times:

Service TypeBusiness Days
Standard5-7
Express2-3
Overnight1
Economy7-10

At registration time, the API takes the upper bound of the transit window, skips weekends (since carriers typically don't deliver on weekends for standard services), and sets the estimated delivery date accordingly.

This is a simplification. Real carrier systems factor in origin/destination zones, holidays, weather disruptions, and capacity constraints. But for registration, a service-type-based calculation is sufficient to set an initial estimate. In Topic 10 (Estimated Delivery Calculation), we will build a dedicated DeliveryEstimationService that provides delivery time windows instead of single dates, distinguishes domestic from international shipments, and supports recalculation when exceptions or delays occur.

Entity Relationships and Foreign Keys

A parcel references two addresses: the shipper's address and the recipient's address. These are foreign key relationships. Before creating a parcel, the API must verify that both addresses actually exist in the database.

1Address (Shipper) <──── Parcel ────> Address (Recipient)
2
3 ├────> TrackingEvent (initial)
4 └────> TrackingEvent (future events)

If either address is missing, the API returns a 404 Not Found with a message indicating which address could not be found. This is a common REST pattern: validate that referenced resources exist before creating a new resource that depends on them.

Request and Response Design

A well-designed registration endpoint separates what the client provides from what the server generates. This distinction keeps the API intuitive and prevents clients from setting values they shouldn't control.

The registration request captures everything needed to describe the parcel:

  • Shipper address ID and recipient address ID -- links to existing addresses
  • Service type -- determines transit speed and estimated delivery
  • Description -- what's inside the package
  • Content items -- structured declarations of each item in the parcel, including HS code (format XXXX.XX), description, quantity, unit value, weight, and country of origin (ISO 3166-1 alpha-2). At least one content item is required for customs compliance on international shipments
  • Weight -- value and unit (e.g., 2.5 kg)
  • Dimensions -- length, width, height, and unit (e.g., 30x20x15 cm)
  • Declared value -- amount and currency (e.g., 150.00 USD)

Content items are essential for international shipping. Customs authorities require each item in a parcel to be declared with its Harmonized System (HS) code, which classifies goods for tariff and regulatory purposes. Even for domestic shipments, content declarations help with insurance claims and regulatory compliance.

The response includes everything from the request plus the server-generated fields:

  • Tracking number -- the newly generated PKT identifier
  • Status -- LabelCreated
  • Estimated delivery date -- calculated from service type
  • Created timestamp -- when the parcel was registered

Notice that weight, dimensions, and declared value are value objects -- they group a numeric value with its unit. This prevents unit mismatch bugs (confusing kilograms with pounds, for example).

Real-World Comparison

Real carrier APIs follow a similar pattern. When you create a shipment through the FedEx or UPS API, the response includes a system-generated tracking number, an initial scan event, and estimated transit times. The details differ (carriers factor in zone charts, surcharges, and capacity), but the workflow is the same: accept input, validate references, generate identifiers, compute derived fields, and return the complete resource.

Building this workflow teaches you patterns that apply well beyond parcel tracking. Any time you create a resource that depends on other resources and needs generated identifiers, you'll follow a similar sequence.

Atomicity and Consistency

The parcel and its first tracking event must be created together. If the parcel is saved but the tracking event fails, the data is inconsistent -- a parcel would exist with no history. This is where transactions come in.

By wrapping the parcel insert and tracking event insert in a single database transaction, you guarantee that either both succeed or both are rolled back. EF Core's SaveChangesAsync handles this naturally when you add both entities to the context before calling save, since all changes within a single SaveChangesAsync call are wrapped in a transaction by default.

Summary

Parcel registration is a multi-step workflow that demonstrates several important API design patterns:

  • Generating unique identifiers for new resources
  • Calculating derived fields from input data and business rules
  • Validating foreign key references before creating dependent resources
  • Creating related entities atomically using transactions
  • Returning the complete resource in the response, including all generated fields

In the next content piece, you'll implement this workflow step by step in an ASP.NET Core controller.