AstroBaaS

Orders & fulfilment

Shipping Label Generation

Paid pluginsize Lplanned, not built

Indicative price, not an offer: $299–699/year

Generated from docs/plan/paid/shipping-label-generation/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Generates carrier-compatible shipping labels (PDF or ZPL) from order data and carrier accounts. Staff print labels, apply to parcels, and drop with carrier. Integrates with carrier APIs to validate format and push shipment data to carrier portal.

The problem

Every shipping label is typed by hand into UPS/FedEx website and printed one at a time. I spend 2 hours daily on labels for 200 orders. No batch processing, no API sync.

What it does

  • Label entity: label_id, order_id, tracking_number, carrier, label_format (pdf or zpl), label_blob_id, created_at, voided_at
  • Generate PDF label: call carrier API with order address, weight, service level; receive PDF stream; store as blob
  • Generate ZPL label: for thermal printers, encode tracking/address as ZPL text; return text for printer firmware
  • Batch label printing: staff select orders, bulk-generate labels, download as zip or print to default printer
  • Tracking number: captured from carrier API response and stored in Order.tracking_number and Shipment.tracking_number
  • Push to carrier: after label generation, POST shipment data to carrier portal to update their system
  • Void label: if staff needs to void (e.g., wrong address), call carrier API to void, mark label voided, issue new label
  • Return label: for refunds/returns, generate return label with merchant’s return address; include in refund email
  • Label history: admin can view all labels for an order (original + voids + returns)
  • Barcode validation: before label generation, validate barcode data (e.g., UPS check digit); reject if invalid

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Thermal printer firmware integration: out of scope—ZPL is text output; merchant handles printer driver setup. Reason: printer-specific; not in scope.
  • Multi-label per order (e.g., 3 boxes, 3 labels): out of scope—one label per shipment. Reason: splits to multi-warehouse and shipment splitting features.
  • International customs forms (CN23): out of scope—label includes country codes; customs form is separate doc. Reason: jurisdiction-specific compliance outside label generation.
  • Address validation before label generation: out of scope—system sends address as-is to carrier; carrier validates. Reason: address-validation is separate feature.
  • Label reprinting from history: out of scope—staff regenerate label. Reason: carrier API may not support reprinting old labels; regenerating is safer.
  • Automatic label printing on order status change: out of scope—staff manually trigger label generation. Reason: async automation is deferred to workflow/rule engine.

Data model

Migration: new ShippingLabel table/collection (label_id, order_id, shipment_id, tracking_number, carrier, label_format, label_blob_id, carrier_response JSON, created_at, voided_at). Add tracking_number to Order and Shipment.

API

  • POST /orders/:id/labels (staff, body: {carrier, service_level, label_format=‘pdf’}) → {label_id, tracking_number, label_url}
  • GET /orders/:id/labels → [{label_id, tracking_number, carrier, created_at, voided_at}]
  • GET /orders/:id/labels/:label_id (returns PDF or ZPL)
  • POST /orders/:id/labels/:label_id/void (staff) → {label_id, voided_at, new_label_id?}
  • POST /labels/batch (staff, body: {order_ids: […]}) → {label_ids: […], zip_url}
  • POST /orders/:id/labels/return (staff, for return label) → {label_id, tracking_number}

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Order detail shows Labels section. List labels with tracking number, carrier, format, created date, void status. Button to generate label (picker for carrier + service level). Batch label generation: select orders, button to bulk-generate, download zip. Return label button for refunded orders.

The seam — why this is paid

Core owns label generation interface, PDF/ZPL formatting, and blob storage. Paid pack owns: thermal printer firmware integration, customs form generation, multi-label per shipment, automatic label generation on triggers. Why: core provides honest single-label generation per order; printer integration and automation are paid.

Requires carrier API credentials and certified integrations. Support commitment includes label format validation, carrier submission failures, and UPS/FedEx account reconciliation.

Dependencies

  • live-carrier-rate-apis (carrier account credentials, rate service levels)
  • shipments-with-tracking-and-carrier-hooks (label linked to shipment)
  • orders (order address, weight)
  • blob-storage (label PDF stored as blob)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • POST /orders/123/labels with carrier=‘ups’, service_level=‘2day’, label_format=‘pdf’ calls UPS API, receives PDF, stores blob, returns tracking_number
  • GET /orders/123/labels/label_456 returns PDF with Content-Type: application/pdf and barcode graphic
  • Label PDF includes order number, recipient address, weight, tracking number, carrier logo, and barcode
  • ZPL format returns plain text with ^FX commands (FedEx/UPS ZPL syntax); no PDF
  • Voiding label calls carrier API with tracking_number; label.voided_at is set; new label can be generated
  • Tracking number stored in Order.tracking_number and accessible via GET /orders/123
  • Batch label generation for 50 orders completes in <5s and returns zip with 50 PDFs
  • Return label includes merchant’s return address, not customer’s; marked as return in label data
  • Attempting to generate label without live-carrier-rate-apis configured returns 400 with ‘carrier.not_configured’
  • Label barcode passes carrier validation (e.g., UPS check digit); invalid barcode returns 400 from carrier API

Risks

Schema: tracking_number is now on both Order and Shipment—must stay in sync or audit finds orphans. API credentials: carrier API keys in environment; if leaked, attacker can generate labels on merchant’s dime. Label storage: if blob storage fails, label is generated but not stored; merchant has tracking number but no PDF. Carrier API: if API is down, label generation fails; staff must retry (async queue recommended but out of scope). Void handling: if void API fails, label marked voided but carrier still has old label; reconciliation needed.

Commercial context

Suggested price$299–699/year
Rival anchorMagento ships PDF label generation for all four bundled carriers free.

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.