PostalForm MCP
Mail real letters from agents: PDF → checkout → status.
Back to PostalForm
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
Copy as Markdown
PostalForm runs a streamable HTTP MCP server. Use the official MCP SDK so session management is handled for you.
- Point your MCP client at /mcp and initialize a session.
- Search sender and recipient addresses to capture address ids and types (drill into Container results with container=id).
- Create an order draft: postalform.create_letter_order_draft (letter text), postalform.create_form_order_draft (workflow forms), or postalform.create_order_draft (PDF).
- Send the customer to checkout_url, or use checkout_session for ChatGPT Instant Checkout.
- 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 Agentic Commerce: Instant CheckoutStripe Shared Payment Tokens
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 US address fields (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": "[email protected]"
},
"payment_data": {
"token": "spt_test_123",
"provider": "stripe"
}
}
}
{
"structuredContent": {
"id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
"buyer": {
"first_name": "Jane",
"last_name": "Doe",
"email": "[email protected]"
},
"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 USPS",
"carrier": "USPS",
"carrier_info": "USPS",
"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.
- US addresses only. Use Loqate address ids returned by postalform.search_addresses, or use manual address input with *_address_type="Manual" and *_address_manual.
- Address suggestions can include type="Container" (buildings/complexes). Call postalform.search_addresses again with container= 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 US address suggestions.
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 US addresses (sender_address_type="Manual" + sender_address_manual). 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
}
}
}
}
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 [email protected].
FAQs
Do I need authentication?
Not currently. Initialize a session and include the mcp-session-id header on requests.
Which checkout path should I use?
Use checkout_url for standard browser checkout. Use complete_checkout only when ChatGPT provides a Stripe shared payment token.
Is this aligned with Stripe Agentic Commerce?
Yes. PostalForm returns ACP checkout sessions and processes Stripe shared payment tokens for ChatGPT Instant Checkout.
Do you support UCP?
Yes. PostalForm publishes a UCP profile at /.well-known/ucp and exposes a UCP MCP endpoint at /ucp/mcp for create/update/complete/cancel checkout sessions.
What limits apply to PDFs?
PDF only, up to 199 pages and 100 MB, via HTTPS download URLs on allowlisted hosts or base64 data URLs.
Ready to send it?
Start by uploading a PDF and we will guide you through the mailing steps.
Try the upload flow
Related use cases
Mail a PDF onlineSend a letter online
View it in markdown
Related Servers
spring-openproject-mcp-server
MCP server to manage OpenProject work-packages realized in Java.
Nexs MCP
NEXS MCP Server is a high-performance implementation of the Model Context Protocol, designed to manage AI elements with enterprise-grade architecture. Built with the official MCP Go SDK v1.1.0, it provides a robust foundation for AI system management.
Amazing Marvin AI Assistant
Connect your Amazing Marvin productivity system with AI assistants for smarter task management.
Todoist
Manage tasks and projects on Todoist using natural language.
Claude MCP Trello
Interact with Trello boards and cards via the Trello API, with built-in rate limiting and error handling.
Plane
The official Plane MCP server provides integration with Plane APIs, enabling full AI automation of Plane projects, work items, cycles and more.
Ablefy Connector
Manage Ablefy digital products, orders, payments, invoices, funnels, and affiliate programs through Claude Desktop. 44 tools with one-click .mcpb installation.
Backlog MCP Server
Interact with the Backlog API to manage projects, issues, wikis, git repositories, and more.
PocketMCP
Turn your Android phone into an MCP (Model Context Protocol) server. AI agents and desktop scripts can call your phone for live data and actions over LAN
Reepl MCP
Create, schedule, and publish LinkedIn posts directly from Claude Desktop or ChatGPT through natural conversations