Shipping
USPS labels for GoCommerce
A Go package that buys a label straight from the United States Postal Service’s APIs when an operator ships an order — no aggregator in between. USPS does not host the label, so the module keeps the PDF and serves it from one admin route.
- Shipping module
- 18 settings
- 9 tests
- 1 API operation
- ext/fulfill-usps
What it does
The module registers “usps” as a fulfillment provider. An operator ships through the engine’s own POST /api/admin/create-fulfillment with the provider usps — the engine keeps the order’s state and its events, and the module only talks to USPS.
USPS needs two credentials to buy one label, and the package walks through the three calls: an OAuth client-credentials token, a payment authorisation tied to the CRID, MID and Enterprise Payment System account that will be charged, and the label itself. Both tokens are cached, so a hundred labels cost a hundred and two calls rather than three hundred.
The label comes back inline, as a base64 PDF. Rather than put a quarter of a megabyte into every order response, the module stores the bytes in its own table and serves them at GET /api/admin/x/fulfill-usps/labels/{tracking} — the one API operation it adds, open only to an operator with orders.read, because a label carries the buyer’s name and address.
Configuration
Eight settings are required: the developer application’s client id and secret, the CRID, MID and payment account from the Business Customer Gateway, a default mail class, and a ship-from street address and ZIP code. Set them in Config, or fill them in under Settings › Shipping providers.
| Setting | Environment variable | Required | What it does |
|---|---|---|---|
ClientIDClient ID | USPS_CLIENT_ID | Yes | The consumer key of a USPS developer application. |
ClientSecretClient secret | USPS_CLIENT_SECRET | Yes | Its consumer secret. With the client id it mints the OAuth bearer token every USPS API takes. |
CRIDCRID | USPS_CRID | Yes | From the Business Customer Gateway. With the MID and the account number it mints the payment authorisation that label creation needs. |
MIDMID | USPS_MID | Yes | The mailer id, from the Business Customer Gateway. |
AccountNumberPayment account number | USPS_EPS_ACCOUNT | Yes | The payment account charged for postage. |
ManifestMIDManifest MID | — | No | The MID that manifests the shipment. Defaults to the MID, which is right for a store with one mailer id. |
AccountTypeAccount type | — | No | EPS, the Enterprise Payment System, by default — in the code’s words, the only one most stores have. |
MailClassMail class | — | Yes | The default service: USPS_GROUND_ADVANTAGE, PRIORITY_MAIL, PRIORITY_MAIL_EXPRESS and the rest. Meta mail_class overrides it for one parcel. |
RateIndicatorRate indicator | — | No | The rate band. SP, single piece, by default; meta rate_indicator overrides it for one parcel. |
ProcessingCategoryProcessing category | — | No | MACHINABLE by default; IRREGULAR, NON_MACHINABLE, LETTERS and FLATS are the others. |
From.StreetAddressFrom: street address | — | Yes | Where parcels are posted from. |
From.ZIPCodeFrom: ZIP code | — | Yes | The ship-from ZIP code. |
From.SecondaryAddress, From.City, From.State, From.FirmFrom: unit, suite; city; state; firm | — | No | The rest of the ship-from address. |
From.FirstName, From.LastName, From.ZIPPlus4, From.Phone | — | No | Also part of the ship-from address, settable in Go only; the package doc’s example sets FirstName. |
DefaultWeightGramsDefault weight (grams) | — | No | Per unit, for a parcel holding a variant with no weight recorded. 500 by default. |
DefaultLengthMM, DefaultWidthMM, DefaultHeightMM | — | No | The box sent when the engine has no unambiguous size, which is any parcel holding more than one unit. 230 × 150 × 50 mm by default. Go only. |
BaseURLAPI base URL | — | No | Overrides https://apis.usps.com, for tests. Empty for production. |
Client | — | No | Replaces the HTTP client, which otherwise times out after 45 seconds. Go only; not in the panel. |
The module reads its Config struct, not the environment. The variable names are the ones the package’s own example or the reference binary uses; in your own main() you choose where each value comes from. Where a setting has a panel label, the admin’s settings drawer can hold it too, and a value typed there wins over Config.
Setting it up
- Get the credentialsA USPS developer application gives the client id and secret; the Business Customer Gateway gives the CRID, the MID and the Enterprise Payment System account that pays for postage.
- Install the moduleImport it and pass usps.New to gocommerce.New, as below — or run the reference binary with -carriers, which installs every carrier module idle.
- Fill in the settingsCredentials, mail class and ship-from address, in Config or under Settings › Shipping providers. A value typed into the panel counts on the next shipment, with no restart.
- Ship and printPOST /api/admin/create-fulfillment with the order id and the provider usps. The shipment’s label URL points at /api/admin/x/fulfill-usps/labels/{tracking}, which serves the PDF.
main.go
import (
"os"
"github.com/itswadesh/gocommerce/core"
usps "github.com/itswadesh/gocommerce/ext/fulfill-usps"
)
app, err := gocommerce.New(cfg,
usps.New(usps.Config{
ClientID: os.Getenv("USPS_CLIENT_ID"),
ClientSecret: os.Getenv("USPS_CLIENT_SECRET"),
CRID: os.Getenv("USPS_CRID"),
MID: os.Getenv("USPS_MID"),
AccountNumber: os.Getenv("USPS_EPS_ACCOUNT"),
From: usps.Address{
FirstName: "Acme", StreetAddress: "4120 Bingham Ave",
City: "St. Louis", State: "MO", ZIPCode: "63116",
},
}),
) The package doc’s own example, with its imports; cfg is your gocommerce.Config. It sets no MailClass, which is required, so as written the module installs idle until a mail class is typed into the panel. Import path github.com/itswadesh/gocommerce/ext/fulfill-usps.
How it works
-
Two tokens, cached
The bearer token is kept until a minute before it expires; the payment authorisation for four hours, half the eight USPS gives it. A 401 drops both, so the next attempt mints a fresh pair rather than retrying a dead one.
-
The label, stored and served
The base64 PDF is decoded and kept in the module’s own table, keyed by tracking number, and served inline for printing to an operator who can read orders — nobody else.
-
Postage is never lost to a missing PDF
If the label image cannot be decoded or stored, the shipment still stands with its tracking number and the failure is logged. Refusing it would leave postage paid for and an order the engine says is unshipped.
-
Rounded up, not down
Weight goes to USPS in pounds and sides in inches, both rounded up to the hundredth. A parcel declared lighter than it is comes back with postage due; one a hundredth heavier costs a cent.
-
ZIP+4 split
A ZIP+4 typed as one string — 94117-1234, or nine digits — is split into the two fields USPS takes, because it rejects the joined form in ZIPCode.
-
Settings without a restart
Before each shipment the panel’s values are laid over Config. With nothing in Config the module installs idle; with every required field in Config it starts switched on.
What it does not do
Read these before an order depends on it. No store is known to run GoCommerce in production yet, so these come from the code, not from anyone’s experience.
- Domestic addresses onlyThe label request carries no country and no customs form, so as written it is for parcels within the United States.
- 4×6 PDF labels onlyThe image type and size are fixed in the code; there is no setting for ZPL or another size.
- An EPS account is neededLabel creation is paid through the payment authorisation, so a store without a CRID, MID and payment account from the Business Customer Gateway cannot buy a label with this module.
- No tracking updatesNo webhook: scans and delivery events at USPS do not reach the order. Marking it delivered is still the operator’s job.
- No cancel callThere is no call to cancel a label. Deleting the fulfillment in GoCommerce does not touch the label or its postage at USPS.
- A guessed box for several itemsFor anything but a single unit, the size sent is the configured default, not a measurement. The engine does not pack cartons.
FAQ
Questions about the USPS module
Where is the label kept?
In the module’s own table in your PostgreSQL database, as PDF bytes keyed by tracking number. GET /api/admin/x/fulfill-usps/labels/{tracking} serves it to an operator with orders.read, and to nobody else. It is the only fulfillment module with a route of its own, because USPS is the only carrier here that does not host its labels.
Why does it need so many credentials?
Because USPS asks for two tokens to buy one label: an OAuth token from the developer application, and a payment authorisation naming the CRID, MID and payment account that pay. Without the second, the package notes, every label call answers 401 with a perfectly valid bearer token.
Can it ship abroad?
Not as written. The label request has no destination country and no customs form, so it is for US domestic parcels.
Is this a USPS partnership?
No. The module calls the public USPS APIs with credentials you supply. USPS does not endorse GoCommerce, and no store is known to run this module in production yet.
Source
Everything on this page is read from ext/fulfill-usps in the GoCommerce repository, MIT licensed. When this page and the code disagree, the code is right and this page is out of date.USPS is a trademark of its owner; this module talks to its public API and implies no endorsement. See trademarks.
Try it against a store of your own
The one-command stack gives you GoCommerce’s API and admin on your own machine in minutes. Add this module to it and try it on test orders before a real one depends on it.