Skip to contentPostalForm

Developers

Mail API and Remote MCP Server for AI Agents

PostalForm is a mail API and remote MCP server for turning PDFs, letters, forms, and agent output into real physical postal mail. Developers can create reviewable mail drafts, hosted checkout sessions, direct MPP/x402 machine orders, and fulfillment-status lookups without building print, envelope, postage, or carrier-handoff infrastructure.

Published Jan 15, 2026 • Updated May 31, 2026

How it works

Step 1

Connect and initialize

Use streamable HTTP at /mcp and keep the mcp-session-id header.

Step 2

Create a draft

Search addresses, then create a priced draft from letter text, a workflow form submission, or a PDF.

Step 3

Take payment and track

Open checkout_url for external payment or use Instant Checkout in ChatGPT, then poll status.

Agentic Commerce Protocol (ACP) / MCP

PostalForm supports the Stripe Agentic Commerce Protocol via our MCP server at /mcp. This is the ChatGPT Apps SDK-compatible flow that returns a checkout_session for Instant Checkout or a checkout_url for hosted checkout.

MCP endpoint: https://postalform.com/mcp

Integration quickstart

PostalForm runs a streamable HTTP MCP server. Use the official MCP SDK so session management is handled for you.

MCP endpoint: https://postalform.com/mcp
  1. Point your MCP client at /mcp and initialize a session.
  2. Search sender and recipient addresses to capture address ids and types (drill into Container results with container=id).
  3. Create an order draft: postalform.create_letter_order_draft (letter text), postalform.create_form_order_draft (workflow forms), or postalform.create_order_draft (PDF).
  4. Send the customer to checkout_url, or use checkout_session for ChatGPT Instant Checkout.
  5. Poll order status as fulfillment progresses.
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(new URL('https://postalform.com/mcp'))
const client = new Client({ name: 'my-agent', version: '1.0.0' })

await client.connect(transport)
await client.listTools()

// Option A: Mail letter text (no PDF upload required).
const draft = await client.callTool({
  name: 'postalform.create_letter_order_draft',
  arguments: {
    letter: {
      title: 'Demand for payment',
      body: 'Hello...\n\nThis is my letter body.\n\nSincerely,\n',
      signature: 'Sender Example',
    },
    sender_name: 'Sender Example',
    sender_address_id: '<address-id>',
    sender_address_type: 'Address',
    sender_address_text: '123 Sender St, Springfield, IL 62701',
    recipient_name: 'Recipient Example',
    recipient_address_id: '<address-id>',
    recipient_address_type: 'Address',
    recipient_address_text: '456 Recipient Ave, Springfield, IL 62701',
  },
})

// Option B: Mail a PDF (attach a PDF in ChatGPT and pass { download_url, file_id }).
// Note: ChatGPT Apps SDK currently has limitations around PDF attachments in some flows.
const pdfDraft = await client.callTool({
  name: 'postalform.create_order_draft',
  arguments: {
    pdf: { download_url: '<chatgpt-download-url>', file_id: '<file-id>' },
    sender_name: 'Sender Example',
    sender_address_id: '<address-id>',
    sender_address_type: 'Address',
    sender_address_text: '123 Sender St, Springfield, IL 62701',
    recipient_name: 'Recipient Example',
    recipient_address_id: '<address-id>',
    recipient_address_type: 'Address',
    recipient_address_text: '456 Recipient Ave, Springfield, IL 62701',
  },
})

// Option C: Mail a workflow-based form.
// 1) postalform.list_forms -> pick slug
// 2) postalform.get_form_schema -> fill required fields (ignore optional checkout_flow metadata)
// 3) postalform.create_form_order_draft -> checkout_url/checkout_session
const formDraft = await client.callTool({
  name: 'postalform.create_form_order_draft',
  arguments: {
    slug: '8822',
    fields: { /* ... */ },
    sender_name: 'Sender Example',
    sender_address_id: '<address-id>',
    sender_address_type: 'Address',
    sender_address_text: '123 Sender St, Springfield, IL 62701',
    recipient_name: 'Recipient Example',
    recipient_address_id: '<address-id>',
    recipient_address_type: 'Address',
    recipient_address_text: '456 Recipient Ave, Springfield, IL 62701',
  },
})

console.log(draft.structuredContent?.checkout_url)

Connection details

  • Endpoint: POST/GET/DELETE /mcp with the streamable HTTP transport.
  • Full MCP URL: https://postalform.com/mcp
  • Base URL: https://postalform.com
  • Sessions: Initialize once, then include the mcp-session-id header on all subsequent requests.
  • Auth: No authentication is required today. Contact us if you need allowlisting.
  • Transport: Streamable HTTP only; JSON-RPC payloads over POST.

Payment and checkout

External checkout (any MCP client)

Use checkout_url from postalform.create_order_draft to send customers to the hosted PostalForm payment page. This works everywhere and does not require ChatGPT-specific payment tokens.

  • Best default for non-ChatGPT MCP clients.
  • Open the URL in a browser (or window.openai.openExternal inside ChatGPT).
  • Poll postalform.get_order_status to confirm payment and fulfillment.

ChatGPT Instant Checkout (Stripe Agentic Commerce)

PostalForm returns an ACP checkout_session in the draft response that ChatGPT uses to show Instant Checkout. When the buyer confirms, ChatGPT calls complete_checkout with a Stripe Shared Payment Token (spt_...).

  • Designed for ChatGPT Apps SDK flows.
  • Use window.openai.requestCheckout(checkout_session) in your UI.
  • complete_checkout processes the shared payment token with Stripe.

Stripe also documents a Stripe-hosted checkout path for agentic apps; PostalForm uses a hosted checkout on its own domain instead. See Stripe Agentic Commerce: Accept a payment for the general framework.

Tool payload examples

Requests below use MCP callTool payloads. Responses show structuredContent snapshots you should parse in your client.

postalform.list_forms + postalform.get_form_schema

Discover published workflow-based forms and fetch a sanitized schema (fields, dependencies, attachments). If present, checkout_flow is informational metadata and does not alter MCP tool sequencing.

{
  "name": "postalform.list_forms",
  "arguments": {
    "q": "IRS",
    "limit": 10
  }
}
{
  "name": "postalform.get_form_schema",
  "arguments": {
    "slug": "8822"
  }
}

postalform.create_letter_order_draft

Server renders a printable PDF from plain text and returns a checkout_url / checkout_session.

{
  "name": "postalform.create_letter_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "letter": {
      "title": "Payment demand letter",
      "body": "Hello,\n\nThis is the letter body.\n\nSincerely,\n",
      "signature": "Sender Example"
    },
    "sender_name": "Sender Example",
    "sender_address_type": "Manual",
    "sender_address_manual": {
      "line1": "123 Sender St",
      "line2": "Apt 4",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701"
    },
    "recipient_name": "Recipient Example",
    "recipient_address_type": "Manual",
    "recipient_address_manual": {
      "line1": "456 Recipient Ave",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701"
    }
  }
}

postalform.create_form_order_draft

Fill a workflow template from JSON fields (and optional attachments), generate a PDF, then return a checkout_url / checkout_session.

{
  "name": "postalform.create_form_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "slug": "8822",
    "fields": {
      "name_line_1": "Sender Example",
      "old_address_line_1": "123 Old St",
      "old_city": "Springfield",
      "old_state": "IL",
      "old_zip": "62701",
      "new_address_line_1": "456 New Ave",
      "new_city": "Springfield",
      "new_state": "IL",
      "new_zip": "62701"
    },
    "sender_name": "Sender Example",
    "sender_address_type": "Address",
    "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "IRS",
    "recipient_address_type": "Manual",
    "recipient_address_manual": {
      "line1": "Internal Revenue Service",
      "line2": "Attn: Form 8822",
      "city": "Kansas City",
      "state": "MO",
      "zip": "64999"
    },
    "use_workflow_recipient": false
  }
}

postalform.search_addresses

Use Address results only. If you see Container types, call again with container=id.

{
  "name": "postalform.search_addresses",
  "arguments": {
    "target": "recipient",
    "query": "123 Main St"
  }
}
{
  "structuredContent": {
    "view": "address_suggestions",
    "target": "recipient",
    "query": "123 Main St",
    "suggestions": [
      {
        "id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
        "text": "123 Main St, Springfield, IL 62701",
        "type": "Address",
        "description": ""
      }
    ]
  }
}

postalform.create_pdf_upload

Use this when you cannot pass ChatGPT file params. Upload the PDF via multipart, then pass the upload_token to postalform.create_order_draft.

{
  "name": "postalform.create_pdf_upload",
  "arguments": {
    "file_name": "letter.pdf",
    "content_type": "application/pdf",
    "content_length": 1234567
  }
}
{
  "structuredContent": {
    "upload_url": "https://postalform.com/api/mcp/pdf-uploads/pfu_...",
    "upload_token": "pfu_...",
    "expires_at": "2026-01-17T12:00:00Z",
    "max_bytes": 104857600,
    "required_headers": {
      "Content-Type": "multipart/form-data"
    }
  }
}

postalform.create_order_draft

Provide sender/recipient names and either Loqate Address ids (type="Address") or manual address fields for a supported mailing country (type="Manual"). Reuse request_id to retry safely.

{
  "name": "postalform.create_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "pdf": {
      "download_url": "https://example.oaiusercontent.com/file.pdf",
      "file_id": "file_abc123"
    },
    "file_name": "letter.pdf",
    "sender_name": "Sender Example",
    "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
    "sender_address_type": "Address",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "Recipient Example",
    "recipient_address_id": "US|LP|Pz0_Qj4_bGJg|199825276|99_ENG",
    "recipient_address_type": "Address",
    "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
    "double_sided": true,
    "color": false
  }
}
{
  "structuredContent": {
    "view": "order_draft",
    "order_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "page_count": 2,
    "price_usd": 2.99,
    "checkout_url": "https://postalform.com/payment?orderId=8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "sender_name": "Sender Example",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "Recipient Example",
    "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
    "checkout_session": {
      "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "payment_provider": {
        "provider": "stripe",
        "merchant_id": "profile_123",
        "supported_payment_methods": ["card", "apple_pay", "google_pay"]
      },
      "status": "ready_for_payment",
      "currency": "usd",
      "line_items": [
        {
          "id": "line_item_8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
          "item": { "id": "postalform_mail_pdf", "quantity": 1 },
          "base_amount": 299,
          "discount": 0,
          "subtotal": 299,
          "tax": 0,
          "total": 299
        }
      ],
      "totals": [
        { "type": "items_base_amount", "display_text": "Items", "amount": 299 },
        { "type": "subtotal", "display_text": "Subtotal", "amount": 299 },
        { "type": "tax", "display_text": "Tax", "amount": 0 },
        { "type": "total", "display_text": "Total", "amount": 299 }
      ],
      "links": [
        { "type": "terms_of_use", "value": "https://postalform.com/terms" },
        { "type": "privacy_policy", "value": "https://postalform.com/privacy" }
      ],
      "payment_mode": "test"
    }
  }
}

complete_checkout

Only for ChatGPT Instant Checkout. payment_data.token must be a Stripe shared payment token (spt_...).

{
  "name": "complete_checkout",
  "arguments": {
    "checkout_session_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "buyer": {
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com"
    },
    "payment_data": {
      "token": "spt_test_123",
      "provider": "stripe"
    }
  }
}
{
  "structuredContent": {
    "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "buyer": {
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com"
    },
    "status": "completed",
    "currency": "usd",
    "line_items": [
      {
        "id": "line_item_8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
        "item": { "id": "postalform_mail_pdf", "quantity": 1 },
        "base_amount": 299,
        "discount": 0,
        "subtotal": 299,
        "tax": 0,
        "total": 299
      }
    ],
    "fulfillment_options": [
      {
        "type": "shipping",
        "id": "postalform_standard",
        "title": "First Class mail",
        "subtitle": "Mailed via postal carrier",
        "carrier": "Postal carrier",
        "carrier_info": "PostalForm print-and-mail provider",
        "earliest_delivery_time": "2025-01-01T00:00:00.000Z",
        "latest_delivery_time": "2025-01-06T00:00:00.000Z",
        "subtotal": 0,
        "tax": 0,
        "total": 0
      }
    ],
    "fulfillment_option_id": "postalform_standard",
    "totals": [
      { "type": "items_base_amount", "display_text": "Items", "amount": 299 },
      { "type": "subtotal", "display_text": "Subtotal", "amount": 299 },
      { "type": "tax", "display_text": "Tax", "amount": 0 },
      { "type": "total", "display_text": "Total", "amount": 299 }
    ],
    "order": {
      "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "checkout_session_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "permalink_url": "https://postalform.com/order/complete?order_id=8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b"
    },
    "messages": [],
    "links": [
      { "type": "terms_of_use", "value": "https://postalform.com/terms" },
      { "type": "privacy_policy", "value": "https://postalform.com/privacy" }
    ]
  }
}

postalform.get_order_status

Poll after payment to track fulfillment progress.

{
  "structuredContent": {
    "view": "order_status",
    "found": true,
    "order_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "is_paid": true,
    "current_step": "letter_created"
  }
}

Integration notes

  • Idempotency: Use request_id on postalform.create_order_draft and reuse the same value on retries. The server will return the existing draft.
  • Errors: Tool calls that fail validation return isError=true with a human-readable message. For Instant Checkout, complete_checkout can also return ACP messages with error codes like payment_declined or requires_3ds.
  • Instant Checkout fallback: If window.openai.requestCheckout is unavailable or checkout_session.payment_provider lacks merchant_id, use checkout_url instead.
  • Order status steps: payment_received, receiver_address_verified, sender_address_verified, pdf_normalized, letter_created, email_sent, canceled, refunded, abandoned.

Example error response:

{
  "isError": true,
  "content": [
    { "type": "text", "text": "recipient_address_manual is required when recipient_address_type is Manual." }
  ]
}

Limits and requirements

  • All orders are printed from PDFs. For postalform.create_order_draft you supply the PDF. For postalform.create_letter_order_draft and postalform.create_form_order_draft we generate the PDF server-side. PDFs are sanitized before printing.
  • Max 199 pages and 100 MB per file.
  • download_url must be HTTPS and hosted on an allowlisted domain (ChatGPT attachments use oaiusercontent.com by default). The URL must be publicly reachable without auth headers, use postalform.create_pdf_upload to get an upload_token, or use a data:application/pdf;base64 URL instead.
  • PostalForm supports U.S., Canadian, and selected international destination countries. Use Loqate address ids returned by postalform.search_addresses, or use manual address input with *_address_type="Manual" and *_address_manual, including countryCode when not U.S.
  • Address suggestions can include type="Container" (buildings/complexes). Call postalform.search_addresses again with container=<id> and a refined query (suite, unit, PO Box) to get type="Address" results. Pass sender_address_type and recipient_address_type from the search results; only type="Address" (not Container) is valid for Loqate-based order drafts.

Tool details

postalform.list_forms

List published workflow-based forms available to agents.

Input: optional q, limit, cursor. Output: form list with slugs, recipient mode, and attachment requirements.

postalform.get_form_schema

Fetch a workflow schema (fields, dependencies, attachments).

Input: slug. Output: sanitized schema derived from workflow.json (no PDF layout internals). The optional checkout_flow field is informational metadata for PostalForm web routing.

postalform.create_pdf_upload

Create a short-lived PDF upload URL + upload_token.

Input: file_name, content_type, content_length, optional request_id for idempotency. Upload the PDF with multipart/form-data (file field) to upload_url, then call postalform.create_order_draft with pdf: { upload_token: "..." }.

postalform.search_addresses

Search address suggestions for supported mailing countries.

Input: query (min 3 chars), optional container (for drilling into Container results), optional target. Output: address suggestions with ids/text/type. If type is Container, call search again with container=id to get Address results for order drafts.

postalform.create_order_draft

Create a draft order and receive a checkout URL.

Input: pdf (ChatGPT file object { download_url, file_id }, or { upload_token } from postalform.create_pdf_upload, or data:application/pdf;base64,..., or an https URL on an allowlisted host), sender/recipient names, and either Loqate address ids/text (sender_address_type="Address") or manual addresses for supported mailing countries (sender_address_type="Manual" + sender_address_manual, including countryCode when not U.S.). Output: order_id, price_usd, checkout_url, checkout_session.

postalform.create_letter_order_draft

Create a draft order from letter text (server renders a PDF).

Input: letter { title?, body, signature? }, sender/recipient addresses (Loqate or Manual), optional print options, optional request_id for idempotency. Output: order_draft payload with checkout_url and checkout_session.

postalform.create_form_order_draft

Create a draft order from a workflow form JSON submission.

Input: slug, fields, optional attachments[{id,base64}], sender address (Loqate or Manual). Recipient can be provided (Loqate/Manual) or routed by the workflow when use_workflow_recipient=true for predefined/computed workflows. Output: order_draft payload with checkout_url and checkout_session.

complete_checkout

Finalize a ChatGPT Instant Checkout session with a payment token.

Input: checkout_session_id (order_id), buyer (first_name, last_name optional, email), payment_data.token (Stripe shared payment token, spt_...), provider=stripe. Output: checkout session response with status and order permalink.

postalform.get_order_status

Fetch the latest order status details.

Input: order_id. Output: found, is_paid, current_step (for example: payment_authorized, payment_received, receiver_address_verified, sender_address_verified, pdf_normalized, letter_created, email_sent, canceled, refunded, abandoned).

postalform.ping

Health check for the MCP server.

Input: none. Output: ping payload for connectivity checks.

Universal Commerce Protocol (UCP)

PostalForm supports the UCP Checkout capability for platforms that integrate via UCP. The discovery profile advertises our UCP MCP endpoint and payment handler configuration. UCP checkouts are dynamically priced based on PDF page count and print options. Include the platform profile in _meta.ucp.profile with each tool call.

Profile: https://postalform.com/.well-known/ucp
UCP MCP: https://postalform.com/ucp/mcp
{
  "name": "create_checkout",
  "arguments": {
    "_meta": {
      "ucp": {
        "profile": "https://platform.example/profiles/v2026-01/shopping-agent.json"
      }
    },
    "currency": "USD",
    "line_items": [
      { "item": { "id": "postalform_mail_pdf_bw_double" }, "quantity": 1 }
    ],
    "payment": {},
    "metadata": {
      "postalform": {
        "pdf": {
          "download_url": "https://example.oaiusercontent.com/file.pdf",
          "file_id": "file_abc123"
        },
        "sender_name": "Sender Example",
        "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
        "sender_address_type": "Address",
        "sender_address_text": "123 Sender St, Springfield, IL 62701",
        "recipient_name": "Recipient Example",
        "recipient_address_id": "US|LP|Pz0_Qj4_bGJg|199825276|99_ENG",
        "recipient_address_type": "Address",
        "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
        "double_sided": true,
        "color": false
      }
    }
  }
}

Protocol and platform sources

What you get

  • • postalform.list_forms
  • • postalform.get_form_schema
  • • postalform.create_pdf_upload
  • • postalform.search_addresses
  • • postalform.create_order_draft
  • • postalform.create_letter_order_draft
  • • postalform.create_form_order_draft
  • • complete_checkout
  • • postalform.get_order_status
  • • postalform.ping

Usage-based billing

Orders start at a low per-letter rate and are billed when checkout completes. You can preview pricing before you charge.

What happens to your document

Secure handling

Your PDF is stored securely and used only to print and mail the letter you requested.

Retention and deletion

We keep files only as long as needed for fulfillment, then delete them on a rolling schedule.

Support

Need help or a custom workflow? Email support@postalform.com.

FAQs

Is PostalForm a mail API?
Yes. PostalForm exposes machine-order REST endpoints, an OpenAPI catalog, and a remote MCP server for creating physical-mail drafts, quotes, machine-payment orders, and fulfillment-status lookups.
Can an MCP server send real postal mail?
Yes, but PostalForm separates draft creation from paid mailing. Draft tools create an unpaid hosted-checkout draft; direct machine-order tools should be used only by authorized runtimes with spending controls and approval.
What is the safest default for ChatGPT, Claude, Gemini, Cursor, or Codex?
Use the remote MCP endpoint at https://postalform.com/mcp, configure approval for mail side effects, create a hosted-checkout draft, and let the user review the PDF, addresses, price, and options before payment.
Can an agent pay for mail with x402 or MPP?
Yes. PostalForm supports direct machine-payment flows for authorized autonomous runtimes. The unpaid call returns a payment challenge, and the paid retry must include the x402 payment signature or MPP authorization.
Do I need a PostalForm API key?
Not for the current public MCP endpoint or machine-payment flow. Some high-volume, abuse-sensitive, or allowlisted deployments may require coordination with support@postalform.com.
Can I upload PDFs through the API or MCP?
Yes. Use the PDF upload tool to create a short-lived upload token, pass an allowlisted HTTPS download URL, use a ChatGPT attachment URL, or provide a data URL where supported.
How do I keep mail side effects safe?
Treat physical mail as a real-world action. Use hosted checkout by default, require human approval, set spend limits, reuse idempotency request IDs, and review preview URLs before payment when present.

Ready to send it?

Start by uploading a PDF and we will guide you through printing, postage, and carrier handoff.