Skip to contentPostalForm

MPP mail

PostalForm MPP mail endpoint instructions

Use this file for MPP-paid document mail only: letters, uploaded PDFs, workflow forms, postcards, and bulk mail campaigns.

Published Jun 17, 2026 • Updated Jun 19, 2026

Endpoints

Purpose Method and path
Discovery probe challenge GET /api/machine/mpp/orders
Validate and quote without creating a payable draft POST /api/machine/mpp/orders/validate
Create unpaid draft, receive 402, and retry after MPP payment POST /api/machine/mpp/orders
Poll payment and fulfillment status GET /api/machine/mpp/orders/{id}

Flow

  1. Build one JSON body for the mail order.
  2. Send it to POST /api/machine/mpp/orders/validate.
  3. If valid, send the same body to POST /api/machine/mpp/orders without Authorization.
  4. Expect 402 plus WWW-Authenticate: Payment ... headers.
  5. Pay one returned challenge.
  6. Retry the exact same POST /api/machine/mpp/orders body with Authorization: Payment <serialized-mpp-credential>.
  7. Read the Payment-Receipt response header.
  8. Poll GET /api/machine/mpp/orders/{id} until payment_status is paid.

Use the same request_id for validate, create, and paid retry. Generate a fresh UUID only for a different mailing.

Supported mail order types

Single recipient letter:

  • Provide letter as a string or object.
  • Optional format: text, html, markdown, or rtf.
  • Optional letter.signature: typed text or drawn signature data.

Uploaded PDF:

  • Provide exactly one pdf source.
  • Preferred shape: { "pdf": { "upload_token": "upload_tok_..." } }.
  • Also accepted: a data URL string or { "download_url": "https://...", "file_id": "..." }.

Workflow form:

  • Discover forms with GET /api/machine/forms.
  • Fetch schema with GET /api/machine/forms/{slug}/schema.
  • Send form: { "slug": "...", "fields": { ... }, "attachments": [...] }.
  • Workflow forms are letter-only.

Postcard:

  • Set mailpiece_type to postcard.
  • Set postcard_size to 4x6, 6x9, or 11x6.
  • Provide pdf only; letter and form are not allowed for postcards.
  • The PDF must follow the published two-page postcard layout guide returned as postcard_guidelines_url.

Bulk mail:

  • Include a top-level bulk object.
  • Include one sender address and no single-recipient fields.
  • Use bulk.content_mode of text, html, or pdf.
  • bulk.csv_content is required.
  • bulk.template_text is required for text mode.
  • bulk.template_html is required for HTML mode.
  • Top-level pdf is required for shared-PDF mode.

Required common fields

Every MPP mail body needs:

  • request_id: UUID idempotency key.
  • buyer_name: buyer or agent owner name.
  • buyer_email: required for machine payment receipt delivery.
  • sender_name.
  • Sender address fields.
  • Mailing options as needed: double_sided, color, mail_class, certified, certified_return_receipt, and signature_required.

For a single-recipient order, also include:

  • recipient_name.
  • Recipient address fields.
  • Exactly one of pdf, letter, or form.

For address fields, use one of these patterns:

  • Loqate address: sender_address_type: "Address", sender_address_id, and sender_address_text.
  • Manual address: sender_address_type: "Manual" and sender_address_manual.

Manual address objects use line1, optional line2, city, state, zip, and optional countryCode.

MPP uses the same country choices as PostalForm's checkout address picker. countryCode and the country prefix on Loqate IDs must be one of US, CA, AT, BE, CH, DE, FR, GB, IN, LU, or NL. Omitted manual-address countries default to US; unsupported or invalid country codes are rejected instead of being treated as U.S. addresses.

Bulk recipient CSVs may use country, country_code, or countrycode. The same country list applies to every row, and omitted countries default to US.

Validate example

curl -sS https://postalform.com/api/machine/mpp/orders/validate \
  -H 'Content-Type: application/json' \
  --json '{
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "buyer_name": "Agent Owner",
    "buyer_email": "owner@example.com",
    "letter": {
      "title": "MPP verification letter",
      "body": "# Hello\n\nThis is a PostalForm MPP verification letter.\n\nSincerely,\nAgent Owner",
      "format": "markdown",
      "signature": "Agent Owner"
    },
    "sender_name": "Agent Owner",
    "sender_address_type": "Manual",
    "sender_address_manual": {
      "line1": "123 Sender St",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701",
      "countryCode": "US"
    },
    "recipient_name": "Recipient Example",
    "recipient_address_type": "Manual",
    "recipient_address_manual": {
      "line1": "456 Recipient Ave",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701",
      "countryCode": "US"
    },
    "double_sided": true,
    "color": false,
    "mail_class": "standard",
    "certified": false
  }'

Expected validation shape:

{
  "protocol": "mpp",
  "methods": ["tempo", "stripe_spt"],
  "status": "validated_new_order",
  "quote": {
    "price_usd": 3.4,
    "currency": "usd",
    "mailpiece_type": "letter"
  }
}

For this guide, use tempo or stripe_spt.

Create and pay

curl -i https://postalform.com/api/machine/mpp/orders \
  -H 'Content-Type: application/json' \
  --json @mpp-mail-body.json

Expected unpaid response:

  • HTTP status 402.
  • X-Payment-Protocol: mpp.
  • One or more WWW-Authenticate: Payment ... headers.
  • JSON body with status: "payment_required".
  • For single-recipient drafts, a preview_url may be present for reviewing the generated PDF before paying.
  • For postcards, postcard_guidelines_url may be present.

Retry after paying one challenge:

curl -i https://postalform.com/api/machine/mpp/orders \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Payment <serialized-mpp-credential>' \
  --json @mpp-mail-body.json

Expected paid response:

  • HTTP status 200 when payment is finalized immediately, or 202 when settlement is accepted and finalization is pending.
  • Payment-Receipt header.
  • JSON body with order_id, payment_status, payment_channel: "mpp_machine", and machine_payment.

Stripe SPT requirements

For the Stripe challenge:

  1. Parse the Stripe challenge from WWW-Authenticate.
  2. Read the challenge amount, currency, and seller network id.
  3. Mint or obtain a buyer-approved spt_... token for exactly those values.
  4. Serialize the original challenge and { "spt": "..." } into one MPP credential.
  5. Send the full serialized credential as Authorization: Payment ....

Do not send only the bare spt_... token.

import { Challenge, Credential } from 'mppx'

const challenges = Challenge.fromResponseList(unpaidResponse)
const stripeChallenge = challenges.find((challenge) => challenge.method === 'stripe')
if (!stripeChallenge) throw new Error('No stripe challenge returned')

const authorization = Credential.serialize(
  Credential.from({
    challenge: stripeChallenge,
    payload: { spt },
  })
)

Tempo requirements

Tempo clients should parse the returned WWW-Authenticate: Payment ... challenge and retry with Authorization: Payment ....

mppx \
  https://postalform.com/api/machine/mpp/orders \
  --account my-tempo-wallet \
  --json-body "$(cat mpp-mail-body.json)" \
  --include \
  --verbose

Poll status

curl -sS https://postalform.com/api/machine/mpp/orders/8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b

Status responses include:

  • awaiting_payment: unpaid draft exists.
  • settled_pending_webhook: payment was accepted, but final payment finalization is pending.
  • paid: payment is finalized and fulfillment has been dispatched or queued.

The {id} path can be the canonical order_id or a request id alias returned by validate/create.

Mail-specific errors

  • 422 invalid_request: fix the payload before creating a payable order.
  • 409 request_id_mismatch: reuse the original JSON body or generate a fresh UUID for a new mailing.
  • 422 missing_buyer_email: add a valid buyer_email.
  • 402 payment_required: normal MPP challenge response.
  • 429 rate_limited: wait before creating more unpaid drafts.

Related MPP docs

  • All MPP endpoints: https://postalform.com/mpp.md
  • Flower-only MPP guide: https://postalform.com/mpp-flowers.md
  • OpenAPI: https://postalform.com/openapi.json