Buy postage as ZPL
Request the ZPL label format when you buy the shipment so the thermal printer gets its native language rather than a rasterized page.
label_format: ZPLIntegration recipe
EasyPost gives you a label the moment you buy postage. RocketPrint puts it on the thermal printer at the bench that is packing the order — no browser, no shared folder, no watcher script.
The recipe
Request ZPL, fetch the bytes in your backend, POST them to the bench printer.
Request the ZPL label format when you buy the shipment so the thermal printer gets its native language rather than a rasterized page.
label_format: ZPLEasyPost returns a URL. Fetch it server-side, where your credentials already are, and base64-encode the bytes.
fetch(label_zpl_url)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-jobsimport RocketPrint from "@rocketprint/node";
const rocketprint = new RocketPrint({ apiKey: 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
We could accept the label URL, but then a machine inside your warehouse would be making outbound requests to URLs your backend supplied. Fetching in your own service keeps that boundary where it belongs. Read the security model →
Store both against the shipment. When support asks about a package, one lookup answers both "where is it" and "did the label print."
Include the bench or printer in the key. A key scoped only to the shipment will replay the first job if you deliberately print the same label in two places.
A failed job carries its reason and the postage is unaffected. Re-send the same bytes once the printer is back.
Questions
No, and that is deliberate. URI-based content types are disabled by default so a station never makes outbound requests to arbitrary URLs. Fetch the label in your backend, where the credentials already live, and send us the bytes.
Ask EasyPost for ZPL when the destination is a thermal printer. If you take a PDF instead, it prints through the station's driver, which decides scaling — workable, but less predictable for a 4×6 label.
Send two jobs with different printer IDs. Use distinct idempotency keys, one per bench, or the second request will replay the first job instead of printing.
The job moves to failed with the reason attached, and postage stays bought. Alert on failed jobs for your fulfillment source and re-send — the label data has not changed.
Next shipment
Install the agent at one bench, create a key, and add three lines after your buy call.