RW

Creating USPS Shipping Labels and Scan Forms With ASP.NET

Published: 2017-04-27 | By: Ryan Williams

Background

I recently needed to start creating USPS shipping labels and Scan Forms for products that need to be shipped on an e-commerce site. Shipping labels are the barcodes that you print and attach to packages and a Scan Form is a bar code that represents many barcodes. The benefit of a Scan Form is that a USPS person can simply scan a single barcode and it will accept up to thousands of individual barcodes at once, they will ask for this if you are doing any significant business volume. 

There are a few different options out there to achive this solution in ASP.NET with C#. First of all, you could integrate directly with the USPS API if you wanted to. If however, you do not want to go that route due to time constraints or want a more generic approach that could work with other shipping providers, you could use EasyPost

 

Why EasyPost?

I decided to go with EasyPost because they had a C# client that was supported by them. It was available as a NuGet package and also on GitHub. EasyPost allows you to work with pretty much every shipping API out there using their API as an abstraction. The costs in using their service are either per label or with a yearly subscription that allows unlimited labels at the provider rate. They may have other options but you would need to contact them for your situation. 

 

How To Get a First Class USPS Label

The following is an example of how I personally decided to buy a shipping label. I use wrappers to decouple my interfaces from EasyPost a bit. If you want a more succinct example without wrappers, then see the EasyPost readme on their GitHub or look at their API docs (their API docs on their site are even better!).

 

public ShippingLabelResponseModel BuyShippingLabel(ShippingLabelRequestModel request)
{
    ShippingLabelResponseModel response;

    try
    {
        var parcel = new Parcel()
        {
            length = request.ShippingPackage.LengthInches,
            width = request.ShippingPackage.WidthInches,
            height = request.ShippingPackage.HeightInches,
            weight = request.ShippingPackage.Weight
        };

        var shipment = new Shipment()
        {
            from_address = ConvertToEasyPostAddress(request.FromAddress),
            to_address = ConvertToEasyPostAddress(request.ToAddress),
            parcel = parcel,
            reference = request.ReferenceId,
            customs_info = RequiresCustomsForm(request) ?
                                SetCustomsInfo(request.Items, request.ShippingPackage.Weight, request.ShippingUserFullName) :
                                null
        };

        shipment.Create();

        shipment.Buy(shipment.LowestRate(
          includeServices: new List() { "First") },
          excludeCarriers: new List() { "FedEx" }
        ));

        response = new ShippingLabelResponseModel()
        {
            ShipmentCreateDate = (DateTime)shipment.created_at,
            ShippingLabelUrl = shipment.postage_label.label_url,
            ShipmentTrackingId = shipment.tracking_code,
            Success = true,
            RequiredCustomsForm = RequiresCustomsForm(request),
            ReferenceId = shipment.reference
        };
    }
    catch (Exception ex)
    {
        Log.Fatal("Failed to buy shipping label, reference id: " + request.ReferenceId, ex);

        response = new ShippingLabelResponseModel()
        {
            Message = "Failed to buy label! " + ex.Message,
            Success = false
        };
    }

    return response;
}

private Address ConvertToEasyPostAddress(EasyPostShippingAddress address)
{
    return new Address
    {
        city = address.City,
        company = address.Company,
        country = address.Country,
        name = address.Name,
        street1 = address.Street1,
        street2 = address.Street2,
        state = address.State,
        zip = address.Zip,
        phone = address.Phone
    };
}

private CustomsInfo SetCustomsInfo(
    List itemsOnOrder,
    double weightOunces,
    string shippingUserFullName)
{
    var items = new List();

    foreach (var item in itemsOnOrder)
    {
        items.Add(new CustomsItem
        {
            description = item.Description,
            quantity = item.Quantity,
            weight = weightOunces,
            origin_country = item.OriginCountry,
            currency = item.Currency,
            value = item.Value
        });
    }

    var info = new CustomsInfo()
    {
        customs_certify = "TRUE",
        eel_pfc = "NOEEI 30.37(a)",
        customs_items = items,
        customs_signer = shippingUserFullName
    };

    return info;
}
 

 

How To Create a USPS Scan Form

The following is how I implemented a USPS Scan Form request. Essentially, I am taking my addresses, building unique shipments with them then creating a batch from these shipments. The batch API is asynchronous so you must poll for a status update of "created" before it is can be used. Also, you must poll for a purchase of the batch in a similar way, waiting for a status of "purchased".

A few things to keep in mind with batches and Scan Forms:

  • All of the shipments on the Scan Form must be from the same address
  • If a batch request fails when you try to buy the labels for it, some of the labels for the shipment labels could be purchased, leaving you in a bad state (example: 1 bad address out of 10 would yeild 9 purchased label shipments but you cannot get a Scan Form from this batch because all of the addresses are not valid)
  • You should use the address verification API prior to purchasing a label, iterate through all of addresses you have and verify them before creating a batch or purchasing the labels on the batch (the batch creation does not verify the addresses, that only happens when you buy the labels and it's not all or nothing)
  • It's best to create the batch and then generate the Scan Form immediatelly afterwards, a Scan Form can only be generated from tracking numbers that were purchased that same day
  • If you expect any international shipments in your batch, make sure you plan for it by collecting all the required info (APO addresses are considered international and thus need a customs form; customs forms labels look differently in shape/ format than domestic labels)
  • It's probably best to use a queue in your application which you poll results for due to the delay in processing the batch/ Scan Form

 


public EasyPostBatchResponse BatchWithScanForm(EasyPostBatchRequest request)
{
    var error = string.Empty;

    var fromAddress = BuildFromAddress(request);

    var shipments = new List>();

    foreach (var item in request.ShippingLabelRequestItems)
    {
        var shipment = BuildShipment(fromAddress, item);

        shipments.Add(shipment);
    }

    try
    {
        var batch = EasyPost.Batch.Create(new Dictionary<string, object>() {
          { "reference", request.ShipmentRequestId },
          { "shipments", shipments}
        });

        if (!CreatedBatchSuccessfully(batch.id, out error))
        {
            return ReturnFailedBatch(batch, "batch creation failed - " + error);
        }

        batch.Buy();

        if (!PurchasedBatchSuccessfully(batch.id, out error))
        {
            return ReturnFailedBatch(batch, "batch buy failed - " + error);
        }

        batch.GenerateScanForm();

        if (!(GeneratedScanFormSuccessfully(batch.scan_form.id)))
        {
            return ReturnFailedBatch(batch, "scan form failed - " + batch.scan_form.id);
        }

        var scanForm = ScanForm.Retrieve(batch.scan_form.id);
        var batchResult = EasyPost.Batch.Retrieve(batch.id);

        return new EasyPostBatchResponse()
        {
            BatchId = batch.id,
            ScanFormUrl = new Uri(scanForm.form_url),
            OrderLabels = GetOrderLabels(batchResult),
            BatchRequestId = batch.reference,
            Success = true
        };
    }
    catch (Exception ex)
    {
        Log.Fatal("Failed to create batch", ex);

        throw new Exception("failed to create batch", ex);
    }
} 
 

Summary

You can quickly use EasyPost's C# NuGet package to purchase labels, create batches, create Scan Forms from a batch, verify addresses and many other important features that USPS offers. You can use their examples or extend their solution with your own. When doing batches, you will need to poll EasyPost's API but it can generate about 100 labels a minute so it's pretty fast. You download the labels from URLs in the responses you get and can then use them in your application for printing or further processing. The Scan Form is accessible as a PDF and when you request a Scan Form from EasyPost, they generate all the labels in the batch in a single PDF that you can use, if you want. 

 



No Comments... Yet


Comment On

Prove you are human 4 + 13 =