Integration recipe

Buy a label with EasyPost. Print it where the box is.

EasyPost gives you a label the moment you buy postage. We put it on the thermal printer at the bench packing that order — no browser, no shared folder, no watcher script.

The recipe

Three steps after the buy.

Ask for ZPL, fetch the bytes in your backend, and send them to the bench printer.

01

Buy postage as ZPL

Ask for the ZPL format when you buy the shipment, so the thermal printer gets its own language instead of a redrawn page.

label_format: ZPL
02

Fetch the label in your backend

EasyPost returns a URL. Fetch it on your server, where your credentials already are, and base64-encode the bytes.

fetch(label_zpl_url)
03

Print it at the bench

Send the encoded ZPL to the printer ID you store for that bench, keyed on the shipment ID so retries are safe.

POST /v1/print-jobs
ship.ts
const RocketPrint = require("rocketprint");

const rocketprint = new RocketPrint(process.env.ROCKETPRINT_KEY);

export async function shipOrder(order, bench) {
  // 1. Buy postage, asking for ZPL because the bench has a thermal printer.
  const shipment = await easypost.Shipment.create({ /* to, from, parcel */ });
  const bought = await shipment.buy(shipment.lowestRate(), { label_format: "ZPL" });

  // 2. Fetch the label server-side. RocketPrint never fetches URLs for you.
  const zpl = await fetch(bought.postage_label.label_zpl_url).then((r) => r.arrayBuffer());

  // 3. Print it where the box actually is.
  const job = await rocketprint.printJobs.create({
    printerId: bench.printerId,
    title: `Label ${bought.tracking_code}`,
    contentType: "raw_base64",
    content: Buffer.from(zpl).toString("base64"),
    source: "easypost",
  }, {
    idempotencyKey: `easypost-${bought.id}-${bench.id}`,
  });

  return { trackingCode: bought.tracking_code, printJobId: job.id };
}

Practical notes

Why the recipe looks like this.

You fetch, we print

We could accept the label URL, but then a computer inside your warehouse would be making web requests to addresses your backend supplied. Fetching it yourself keeps that boundary where it belongs. Read the security model →

Keep the tracking code and the job ID together

Store both against the shipment. When support asks about a parcel, one lookup answers both "where is it" and "did the label print."

Idempotency keys are per bench

Include the bench or printer in the key. A key based only on the shipment will return the first job if you deliberately print the same label in two places.

Failed prints do not waste postage

A failed job tells you why, and your postage is unaffected. Send the same bytes again once the printer is back.

Questions

EasyPost-specific questions.

Does RocketPrint fetch the label URL for me?

No, and that is deliberate. We do not fetch label URLs, so a computer in your warehouse never makes web requests to whatever address was passed in. Fetch the label in your own backend, where your credentials already live, and send us the bytes.

What label format should we request?

Ask EasyPost for ZPL when it is going to a thermal printer. A PDF prints through that computer's driver, which decides scaling — workable, but less predictable for a 4×6 label.

Can we print the same label to two benches?

Send two jobs with different printer IDs. Use a different idempotency key for each bench, or the second request will return the first job instead of printing.

What if the buy succeeds but the print fails?

The job is marked failed with the reason attached, and your postage stays bought. Alert on failed jobs and send it again — the label has not changed.

Next shipment

Print the next label you buy.

Install the Station app at one bench, create a key, and add three lines after your buy call.