PostalForm Send Mail
Purpose
Place a real print-and-mail order on PostalForm — upload (or compose) a document, attach a sender and recipient address, choose mail options, pay, and receive a tracked order ID. PostalForm prints the document, prepares the envelope and postage, and hands it to a carrier (USPS via Lob / PostGrid for letters, Click2Mail for Express, Florist One for flower letters). Both letters (PDF, DOCX, Markdown, HTML, RTF, or plain text) and postcards (4x6, 6x9, 11x6 PDF) are supported. This skill is not read-only — it places a paid order on the owner's behalf.
PostalForm does not ship postcards through a
/postcardsweb flow —/postcards.md404s. Postcards are an API-only product today; see Site-Specific Gotchas.
When to Use
- Mailing a signed letter, demand notice, IRS form (1099-NEC, 1120, 5471, 8822, 843, etc.), Certified Mail packet, or any document a recipient needs in physical mail.
- Mailing a postcard (artwork side + blank mailing side — PostalForm fills the mailing block automatically).
- Bulk mail merges (one CSV → one mailpiece per row with
{{column}}template variables). - Same-business-day USPS handoff for U.S. Express orders submitted before 10:00 AM Eastern.
- Any agent flow where the owner wants the agent to make a real-world mailing happen autonomously (x402 USDC, MPP Tempo/Stripe SPT, or MCP draft-and-checkout).
Workflow
PostalForm is one of a handful of sites that publishes a first-class agent-facing API at https://postalform.com/agents, with HTTP-402 machine payments (x402 + MPP), a streamable MCP server at /mcp, a UCP shopping-checkout binding at /ucp/mcp, and an OpenAPI document at /openapi.json. Prefer the API over scripted browsing — the browser flow uses Stripe Elements + Inertia.js multi-step state that is brittle to drive, while the API is documented, idempotent, validate-first, and supports both stablecoin and Stripe Shared Payment Tokens. Browser is the fallback for human checkout or when machine payments are unavailable.
Method A — x402 (USDC on Base) — recommended for crypto-capable agents
-
Read discovery once and cache.
GET https://postalform.com/.well-known/x402→ range pricing, network, facilitator (https://api.cdp.coinbase.com/platform/v2/x402), token (USDC), eip155:8453.GET https://postalform.com/openapi.json→ per-route input schemas andinfo.guidanceplanning hints.
-
Build address strategy. Pick exactly one per party:
- Manual (when you already have line1/city/state/zip) — set
*_address_type: "Manual"and*_address_manual: { line1, line2?, city, state, zip, countryCode? }. - Loqate (when you only have partial info) — call
postalform.search_addressesvia MCP or the in-page autocomplete via browser; resolve totype="Address"(never"Container"— drill down with acontainerparam first). Set*_address_type: "Address",*_address_id: "US|LP|...", and*_address_text: "${text}, ${description}". - You may mix: manual sender + Loqate recipient (or vice versa).
- Manual (when you already have line1/city/state/zip) — set
-
Draft the order payload. Required fields:
request_id— UUID (idempotency key; reuse it for the unpaid + paid retry of the same logical order; generate a fresh UUID for a different mailing).buyer_name,buyer_email(the email is set as Stripereceipt_email).- Exactly one document source:
pdf: { upload_token }(preferred canonical form), or{ download_url, file_id }, or top-level"data:application/pdf;base64,..."string, or an allowlisted https URL.letter: { format: "text"|"html"|"markdown"|"rtf", title?, body, signature? }— PostalForm server-renders the PDF.form: { …workflow-form payload… }— discovered viaGET /api/machine/forms+GET /api/machine/forms/{slug}/schema.
- Sender + recipient name and address per step 2.
- Options (all optional with sane defaults):
double_sided(defaulttrue)color(defaultfalse)mail_class—"standard"(First Class),"priority","express"certified(defaultfalse; First Class only — combining with Express is rejected at validate)certified_return_receipt(defaultfalse; ignored unlesscertified=true)mailpiece_type: "letter"(default) or"postcard"; postcards also requirepostcard_size: "4x6" | "6x9" | "11x6".
-
Validate first (no payment side effects).
POST https://postalform.com/api/machine/orders/validate Content-Type: application/json <payload>Returns
200with{ request_id, request_hash, order_id: null, status: "validated_new_order", quote: { price_usd, full_price_usd, billable_page_count, provider, ... } }. Verified: a 1-page B&W double-sided standard letter quotes $3.40; the same letter withcertified=true, certified_return_receipt=truequotes $14.40. Validation errors come back as422 { code: "invalid_request", errors: [{ path, message, hint, fix_examples }] }— readfix_examplesbefore retrying. -
Create the order (unpaid). Same body, hit:
POST https://postalform.com/api/machine/ordersExpect
402 Payment Requiredwith aPAYMENT-REQUIREDheader describing amount, asset, network, facilitator, and any nonce/recipient address. -
Pay and retry. Sign the x402 challenge (Coinbase CDP facilitator handles settlement), put the proof in the
PAYMENT-SIGNATUREheader, and re-POST the exact same body (including the samerequest_id). PostalForm settles via the facilitator and returns202 Acceptedwith aPAYMENT-RESPONSEheader and{ order_id, status, ... }. -
Poll status until terminal:
GET https://postalform.com/api/machine/orders/{order_id}States progress:
awaiting_payment→paid→queued→processing→printed→handed_off→in_transit→delivered. For Certified Mail, USPS tracking number appears once carrier acceptance occurs.
Method B — MPP (Tempo crypto or Stripe Shared Payment Token)
Same 4-step shape, swap endpoints + header conventions:
POST /api/machine/mpp/orders/validate— quote.POST /api/machine/mpp/orders(noAuthorization) →402+WWW-Authenticate: Payment ….- Pay with Tempo (testnet for staging, mainnet live) or mint a Stripe
spt_…Shared Payment Token via the Stripe Link CLI (link auth loginthenlink spt mint); retry withAuthorization: Payment <serialized-MPP-credential>. - Server returns
202+Payment-Receipt; status atGET /api/machine/mpp/orders/{id}. Wait forpayment_status: "paid"(Stripe webhook finalization).
Method C — MCP (postalform server at /mcp)
Streamable HTTP MCP server. Use when your agent runtime already speaks JSON-RPC 2.0 MCP.
POST /mcpwithinitialize→ capture themcp-session-idresponse header (server is stateful; subsequent calls must echo this header or you get400 No valid session ID provided).tools/list→ 12 tools exposed:postalform.list_forms,postalform.get_form_schema— workflow forms catalogpostalform.search_addresses— Loqate autocomplete withContainerdrill-downpostalform.create_pdf_upload— get an upload token for a PDFpostalform.create_order_draft,postalform.create_letter_order_draft,postalform.create_form_order_draft,postalform.preview_letter_order_draft— build a draft (no payment)postalform.create_machine_order— direct machine order (validate + pay + create in one call)complete_checkout— Instant Checkout for clients that prefer hosted-Stripe paymentpostalform.get_order_status,postalform.ping
- For a draft → hosted-Stripe flow:
create_order_draftreturns a checkout URL the owner (or agent w/ card credential) opens. - For an end-to-end machine flow:
create_machine_orderperforms the validate-and-pay handshake internally and returnsorder_id.
Method D — UCP Checkout (shopping-agent platforms only)
PostalForm exposes the UCP Checkout capability at https://postalform.com/ucp/mcp with dev.ucp.shopping.checkout (version 2026-01-11). Tools: create_checkout, get_checkout, update_checkout, complete_checkout, cancel_checkout. Limitations: UCP supports PDF-based checkouts with Loqate-resolved addresses only; manual addresses, server-rendered letters, postcards, and form workflows require the regular MCP path. Every UCP call must include _meta.ucp.profile pointing to the platform's UCP profile URL.
Browser fallback
Use when the agent cannot pay via x402/MPP and needs a human (or Browserbase + saved card) to complete Stripe checkout. Stealth + residential proxy are not required — site is Cloudflare-fronted but no anti-bot wall observed.
browse open https://postalform.com/→ file input ref appears next to "Upload your Document" (h1: "Upload a File. We print and mail it for you.").browse upload @<file-input-ref> /path/to/letter.pdf→ the SPA replaces the upload card with the recipient address autocomplete (h1: "Where should we send your document to?") without changing the URL (stays athttps://postalform.com/). Wait ~2–3 s for the Inertia re-render before snapshotting.- Click the recipient address combobox, type 3+ chars, wait 1.5–2 s for Loqate dropdown, click
menuitemfor the target address (drill intoContainerfirst if it's a building/complex). - Repeat for sender. Choose print options (color/B&W, single/double-sided, First Class/Express, Certified) — these are surfaced as checkboxes/radios on the same page.
- Final step is a Stripe Elements card form. Do not complete checkout from an agent session unless the owner has explicitly authorized payment via a Browserbase-stored card.
- Order confirmation page contains the
order_id; sameGET /api/machine/orders/{id}endpoint works for tracking.
For known-template flows there are shortcuts:
https://postalform.com/forms/plain-letter— write a letter in the in-page editor (no PDF).https://postalform.com/forms/1099-nec(and other slugs from/api/machine/forms) — guided form workflow that builds the PDF for you.https://postalform.com/credit-report-dispute-packets,/bulk-mail,/flowers— verticals with their own UIs.
Site-Specific Gotchas
- Postcards have no
/postcardsweb page — they're API-only./postcards.mdreturns 404 even though the top-nav has a "Postcards" link (it redirects through guidelines). To mail a postcard you must use the machine API withmailpiece_type: "postcard"+postcard_size: "4x6" | "6x9" | "11x6"and supply a 2-page PDF: page 1 = artwork side, page 2 = mailing side (leave the mailing block blank — PostalForm typesets sender/recipient/indicia/barcode automatically; if you put them in the PDF, the order will be rejected or print wrong). Use the official templates:/postcard-guidelines/us_intl_postcard_6inx4in.pdf,…9inx6in.pdf,…11inx6in.pdf. Server normalizes postcard options tocolor=true, double_sided=true, mail_class=standard, certified=false, signature_required=false— don't try to override. request_idis the idempotency key, not just a log tag. Reuse the same UUID for the unpaidPOSTand the paid retry of the same logical mailing; generate a fresh UUID for a legitimately new order. Reusing across distinct orders returns the prior order and skips charge — fine for retry, dangerous for "send the same letter to a second person".buyer_emailis required. PostalForm passes it to Stripe asreceipt_email. Missing →422 invalid_request.- Loqate
ContainerIDs fail late. A suggestion withtype="Container"is a building, not an address. Sending it as*_address_idpasses initial JSON validation but fails Loqate verification after payment, leaving the order in a stuck state. Always drill down withcontainer=<id>+ a refined query until you gettype="Address". *_address_typemust match the field shape. Mixing*_address_idwithManual, or*_address_manualwithAddress, is silently confusing — validate will accept one but ignore the other. Send exactly one shape per party.pdfmust be a JSON object, except for data URLs.{ "pdf": "data:application/pdf;base64,..." }is fine as a top-level string, but{ "pdf": { "data_url": "..." } }fails with422 pdf object must be either { upload_token } or { download_url, file_id }(verified). Canonical preferred form is{ upload_token }frompostalform.create_pdf_upload.- Certified Mail is First Class only.
certified=true+mail_class="express"→422at validate.certified_return_receipt=trueis silently ignored unlesscertified=true. Lob fulfills Certified up to 59 single-sided / 118 double-sided PDF pages; over that, PostalForm routes through PostGrid and Certified is unavailable (the validate response will report the routing). - Pricing tiers kick in by sheet count, not page count. Base $3.00 + $0.20/page B&W or $0.40/page color, plus a flat tier fee that depends on billable sheets: double-sided thresholds 12/120/200 → $5.50 / $10.88 / $22.38; single-sided thresholds halve to 6/60/100. Only the highest tier applies. Billable pages = source pages + 1 (an inserted address page on First Class/Standard). Express uses raw PDF pages (no inserted page) and a different click2mail formula. Source of truth: the Python model at
/pricing-calculator.mdmirrors the server math exactly; the checkout/validate quote is the binding price. - Express same-business-day cutoff is 10:00 AM Eastern. Orders placed after the cutoff (or on weekends/federal postal holidays) reach USPS the next business day. Production + carrier handoff is documented as 1–3 business days regardless of service.
- Cancellation has a tight window. Once an order enters printing or carrier handoff it is generally non-cancelable and non-refundable. If the agent owner wants out, hit
support@postalform.comimmediately and reference theorder_id. Carrier delays/loss/forwarding after USPS acceptance are USPS issues, not PostalForm issues — don't open a refund dispute for those. - MCP needs
mcp-session-idechoed. Afterinitialize, the response carriesMcp-Session-Id. Every subsequent JSON-RPC call must includemcp-session-id: <value>or the server responds400 No valid session ID provided. Trivial to miss — verified during recon. - UCP can't carry every order shape. UCP create_checkout only supports
mailpiece_type: "letter"+ PDF source + Loqate addresses (*_address_type="Address"only). Postcards, manual addresses, server-rendered letters, and workflow forms require the regular MCP / x402 / MPP path. - Markdown alternates exist for every public page. Append
.mdto any URL (/agents.md,/pricing.md,/send-letter-online.md,/delivery-times.md,/help/what-happens-after-you-mail.md, etc.) to get clean markdown — much cheaper than rendering the SPA. Notable 404s among plausible slugs:/postcards.md,/mcp.md,/flowers.md,/bulk-mail.md(those features exist but only as live SPA routes or API endpoints). - Inertia.js + same-URL transitions. Uploading a file or clicking through steps mutates the page component without changing
window.location. Don't poll the URL to detect progress —browse snapshotthe page and look for the next heading ("Where should we send your document to?","Confirm your sender address","Choose print and delivery options", etc.). - No anti-bot wall today. Verified
--verified --proxiesand bare sessions both load cleanly. The site is Cloudflare-fronted (Server: cloudflare,Cf-Ray: ...) but no challenges fired during recon. Don't waste budget on stealth unless you start seeing 403s. - Tempo & Stripe SPT are listed in the
.well-known/x402manifest as separate payment instruments under MPP — Tempo for crypto, Stripe SPT for card-via-Link. If your runtime doesn't speak either, fall back to x402 (USDC on Base) or the MCP draft + hosted-Stripe checkout flow.
Expected Output
Four distinct outcome shapes (validate / paid-created / status / error). All amounts are in cents-as-float USD.
Validate (no payment side effects)
{
"request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"request_hash": "9c1835a78c5f93cb6ffba285603ee1452e718806c1852a1f05b28687c5682342",
"order_id": null,
"status": "validated_new_order",
"postcard_guidelines_url": null,
"quote": {
"price_usd": 3.4,
"full_price_usd": 3.4,
"currency": "usd",
"page_count": 1,
"billable_page_count": 2,
"double_sided": true,
"color": false,
"mail_class": "standard",
"certified": false,
"certified_return_receipt": false,
"signature_required": false,
"mailpiece_type": "letter",
"postcard_size": null,
"provider": "lob"
}
}
Order created (after 202 Accepted payment retry)
{
"request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"order_id": "ord_01HXY0Z…",
"status": "paid",
"quote": { "price_usd": 14.4, "certified": true, "certified_return_receipt": true, "...": "..." },
"payment": {
"protocol": "x402",
"method": "usdc-base",
"tx_hash": "0x…",
"amount_usd": 14.4
}
}
Status poll (GET /api/machine/orders/{id} or …/mpp/orders/{id})
{
"order_id": "ord_01HXY0Z…",
"status": "in_transit",
"payment_status": "paid",
"mail_status": "handed_off",
"carrier": "usps",
"service": "first_class_certified_return_receipt",
"tracking_number": "9407…",
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9407…",
"expected_delivery": "2026-06-02",
"events": [
{ "at": "2026-05-28T19:42:11Z", "event": "queued" },
{ "at": "2026-05-29T14:08:22Z", "event": "printed" },
{ "at": "2026-05-29T20:31:00Z", "event": "carrier_accepted" }
]
}
Validation error (422 from validate or unpaid create)
{
"message": "Validation failure",
"code": "invalid_request",
"errors": [
{
"path": "pdf",
"code": "custom",
"message": "pdf object must be either { upload_token } or { download_url, file_id }.",
"hint": "Prefer a canonical PDF input: { \"pdf\": { \"upload_token\": \"<token>\" } }. The other accepted formats are data URL or { download_url, file_id }.",
"fix_examples": [
{ "pdf": { "upload_token": "upload_tok_123" } },
{ "pdf": { "download_url": "https://files.openai.com/v1/files/file_123/content", "file_id": "file_123" } },
{ "pdf": "data:application/pdf;base64,JVBERi0xLjQK..." }
]
}
]
}