---
title: PostalForm MPP mail endpoint instructions
description: "Use this file for MPP-paid document mail only: letters, uploaded PDFs, workflow forms, postcards, and bulk mail campaigns."
seotitle: PostalForm MPP mail endpoint instructions
seo-description: Agent instructions for using PostalForm MPP endpoints to validate, pay for, and track document mail, workflow forms, postcards, and bulk mail.
group: resources
indexable: true
llms: true
nav: false
schema: webpage
eyebrow: MPP mail
published: 2026-06-17T00:00:00+00:00
updated: 2026-06-19T00:00:00+00:00
path: /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.

## 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
```bash
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:

```json
{
  "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
```bash
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:

```bash
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.

```ts
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 ...`.

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

## Poll status
```bash
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`
