Build with Postcard.bot

Let AI agents send real physical postcards worldwide. Integrate via MCP server or REST API.

MCP Compatible
REST API
From $0.69 USA
Quick Start
Get up and running in 3 steps
1

Get an API key

Sign up at postcard.bot, go to your account page, and generate an API key in the API Keys tab.

2

Add balance

Add credits via postcard.bot/buy-credits or the POST /api/v1/credits endpoint. Prepaid balance - funds must be available before sending. Current volume pricing starts at $0.69 USA / $1.89 international.

3

Configure your MCP client

Add the MCP server to Claude Desktop, Claude Code, or any MCP-compatible client.

Remote MCP Server
Recommended
Zero setup — just paste a URL and sign in with your Postcard.bot account

The fastest way to get started. No API key needed, no local install — OAuth handles authentication automatically. Works with Claude Desktop, Claude Code, ChatGPT, and any MCP client that supports remote servers.

https://postcard.bot/api/mcp

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

claude_desktop_config.json
{
  "mcpServers": {
    "postcardbot": {
      "url": "https://postcard.bot/api/mcp"
    }
  }
}

Claude Code

claude mcp add --transport http postcardbot https://postcard.bot/api/mcp

How it works

  1. Add the URL to your MCP client
  2. Your client opens a browser window for you to sign in
  3. Sign in with Google or email (same Postcard.bot account)
  4. Click "Allow" to authorize access
  5. Start sending postcards — your balance and pricing tier apply automatically
Local MCP Server (npm)
Run the MCP server locally with an API key

Install via npm

npx -y @postcardbot/mcp-server

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

claude_desktop_config.json
{
  "mcpServers": {
    "postcardbot": {
      "command": "npx",
      "args": ["-y", "@postcardbot/mcp-server"],
      "env": {
        "POSTCARDBOT_API_KEY": "pk_live_your_key_here"
      }
    }
  }
}

Claude Code

claude mcp add postcardbot -- npx -y @postcardbot/mcp-server

Then set POSTCARDBOT_API_KEY in your shell environment.

MCP Tools Reference
Tools available through the MCP server

send_postcard

POST

Send a physical postcard. Cards are printed and shipped within 24 hours. Delivery takes 5-10 business days.

Parameters:

  • to — Recipient address (name, address_line1, city required; address_line2, state, zip, country optional)
  • from — Sender/return address (same fields)
  • message — Back-of-card message (max 350 characters)
  • image_url — Front image URL (publicly accessible, min 1875x1275px recommended)

Example prompt:

"Send a postcard to Jane Doe at 123 Main St, San Francisco CA 94102 with a photo of the Golden Gate Bridge and the message 'Wish you were here!'"

bulk_send

POST

Send the same postcard to multiple recipients (async). Up to 5,000 recipients per request. Cards are processed in background batches (~25/minute).

Parameters:

  • recipients — Array of recipient addresses (max 5,000, same fields as to)
  • from — Sender/return address (same for all postcards)
  • message — Back-of-card message (max 350 characters)
  • image_url — Front image URL (publicly accessible, min 1875x1275px recommended)

Example prompt:

"Send a holiday postcard to my 3 friends: Jane at 123 Main St SF CA, John at 456 Oak Ave NYC NY, and Bob at 789 Pine Rd Austin TX"

Returns a bulk_id immediately. Total cost is reserved upfront. Failed cards are automatically refunded. Poll status_url for progress.

check_balance

GET

Check account balance, total postcards sent, and current volume pricing tier. Use this before sending to know your per-postcard cost.

get_pricing

GET

Get all volume pricing tiers. No parameters required.

check_status

GET

Check delivery status of a previously sent postcard.

Parameters:

  • postcard_id — The ID returned from send_postcard

create_webhook

POST

Register a URL to receive real-time postcard event notifications. Events are signed with HMAC-SHA256. Max 10 webhooks per account.

Parameters:

  • url — HTTPS URL to receive webhook POST requests
  • events — Event types: postcard.sent, postcard.delivered, postcard.failed, postcard.returned

The signing secret is returned only on creation — save it securely.

list_webhooks

GET
/

delete_webhook

DELETE

List all registered webhooks or delete one by its webhook_id.

REST API Reference
Direct HTTP API for custom integrations

All API requests require authentication via Bearer token. Include your API key in the Authorization header.

Authorization: Bearer pk_live_your_key_here
POST
/api/v1/postcards

Send a postcard

Request body
{
  "to": {
    "name": "Jane Doe",
    "address_line1": "123 Main St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94102",
    "country": "US"
  },
  "from": {
    "name": "John Smith",
    "address_line1": "456 Oak Ave",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "country": "US"
  },
  "message": "Wish you were here!",
  "image_url": "https://example.com/golden-gate.jpg",
  "image_fit_mode": "cover",
  "back_template": "classic"
}

Use classic or heritage for the postcard back style.

Response
{
  "id": "pc_abc123",
  "status": "sent",
  "expected_delivery_date": "2025-03-15",
  "price": 1.99,
  "back_template": "classic",
  "balance_remaining": 18.01
}
POST
/api/v1/postcards/preview

Validate price, image dimensions, and front QR before sending. This does not mail a card or deduct balance.

Request body
{
  "to": {
    "name": "Jane Doe",
    "address_line1": "123 Main St",
    "city": "San Francisco",
    "state": "CA",
    "zip": "94102",
    "country": "US"
  },
  "from": {
    "name": "John Smith",
    "address_line1": "456 Oak Ave",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "country": "US"
  },
  "message": "Scan the QR code on the front to see your preview.",
  "image_url": "https://example.com/postcard-front-with-qr.png",
  "back_template": "heritage"
}

Preview returns the selected back style and print-readiness checks without sending mail or deducting balance.

Response
{
  "mode": "preview",
  "will_send": false,
  "can_send": true,
  "back_template": "heritage",
  "price": 0.99,
  "qr": {
    "front_print_detected": true,
    "decoded_url": "https://example.com/landing-page"
  },
  "warnings": []
}
POST
/api/v1/postcards/bulk

Send postcards to multiple recipients — async (up to 5,000)

Request body
{
  "recipients": [
    { "name": "Jane Doe", "address_line1": "123 Main St", "city": "San Francisco", "state": "CA", "zip": "94102", "country": "US" },
    { "name": "John Smith", "address_line1": "456 Oak Ave", "city": "New York", "state": "NY", "zip": "10001", "country": "US" }
  ],
  "from": { "name": "Acme Inc", "address_line1": "789 Pine Rd", "city": "Austin", "state": "TX", "zip": "78701", "country": "US" },
  "message": "Happy holidays from Acme!",
  "image_url": "https://example.com/holiday-card.jpg",
  "back_template": "classic"
}
Response (202 Accepted)
{
  "bulk_id": "bulk_1710000000_abc123",
  "status": "queued",
  "total": 2,
  "total_cost": 3.98,
  "status_url": "https://postcard.bot/api/v1/postcards/bulk/bulk_1710000000_abc123",
  "message": "Job queued. 2 postcards will be processed shortly. Poll status_url for progress."
}

Cost is reserved upfront. Cards are processed in background batches (~25/minute). Failed cards are automatically refunded. Poll status_url to check progress.

GET
/api/v1/postcards/bulk/:bulk_id

Check bulk job progress

Response
{
  "bulk_id": "bulk_1710000000_abc123",
  "status": "completed",
  "total": 500,
  "processed": 500,
  "succeeded": 498,
  "failed": 2,
  "total_cost": 492.02,
  "created_at": "2026-03-10T12:00:00Z",
  "completed_at": "2026-03-10T12:20:00Z",
  "postcard_ids": ["pc_abc1", "pc_abc2", "..."],
  "failures": [
    { "index": 42, "to": "Bad Address, Nowhere", "error": "Invalid address" }
  ]
}
GET
/api/v1/balance

Check account balance and current pricing tier

Response
{
  "balance": 47.50,
  "currency": "USD",
  "lifetime_top_up": 150.00,
  "tier": { "number": 3, "name": "Silver" },
  "current_pricing": {
    "usa": 1.29,
    "international": 2.29
  },
  "top_up_url": "https://postcard.bot/buy-credits"
}
POST
/api/v1/credits

Add credits via Stripe checkout (min $5, max $10,000)

Request body
{
  "amount": 50.00
}
Response
{
  "checkout_url": "https://checkout.stripe.com/...",
  "amount": 50.00,
  "currency": "USD"
}
GET
/api/v1/postcards/:id

Check postcard status

Response
{
  "id": "pc_abc123",
  "status": "sent",
  "delivery_status": "dispatched",
  "delivery_status_label": "Dispatched",
  "expected_delivery_date": "2025-03-15",
  "mail_events": [
    {
      "status": "processed_for_printing",
      "label": "Processed for printing",
      "occurred_at": "2025-03-10T12:01:00Z"
    },
    {
      "status": "dispatched",
      "label": "Dispatched",
      "occurred_at": "2025-03-11T12:01:00Z"
    }
  ],
  "to": {
    "name": "Jane Doe",
    "city": "San Francisco",
    "country": "US"
  },
  "created_at": "2025-03-10T12:00:00Z",
  "sent_at": "2025-03-10T12:01:00Z"
}
GET
/api/v1/pricing

Get pricing tiers (no authentication required)

Response
{
  "postcards": {
    "usa": {
      "price": 1.99,
      "currency": "USD"
    },
    "international": {
      "price": 2.99,
      "currency": "USD"
    }
  },
  "minimum_top_up": 5,
  "pricing_notice": "Current USD rates. Prices may change over time due to postage, printing, inflation, taxes, currency, or operating cost changes. The applicable price is shown before checkout or before an API send deducts balance.",
  "tier_notice": "Lifetime top-up progress does not expire. Per-card prices may change over time.",
  "volume_tiers": [
    {
      "tier": 1,
      "name": "Starter",
      "min_top_up": 1,
      "max_top_up": 19,
      "usa_price": 1.49,
      "international_price": 2.49
    },
    {
      "tier": 2,
      "name": "Bronze",
      "min_top_up": 20,
      "max_top_up": 49,
      "usa_price": 1.29,
      "international_price": 2.29
    },
    {
      "tier": 3,
      "name": "Silver",
      "min_top_up": 50,
      "max_top_up": 199,
      "usa_price": 0.99,
      "international_price": 2.19
    },
    {
      "tier": 4,
      "name": "Gold",
      "min_top_up": 200,
      "max_top_up": 499,
      "usa_price": 0.85,
      "international_price": 2.09
    },
    {
      "tier": 5,
      "name": "Platinum",
      "min_top_up": 500,
      "max_top_up": 999,
      "usa_price": 0.79,
      "international_price": 1.99
    },
    {
      "tier": 6,
      "name": "Diamond",
      "min_top_up": 1000,
      "max_top_up": null,
      "usa_price": 0.69,
      "international_price": 1.89
    }
  ]
}
POST
/api/v1/webhooks

Register a webhook URL for postcard event notifications. Max 10 per account. HTTPS only.

Request body
{
  "url": "https://example.com/webhooks/postcards",
  "events": ["postcard.created", "postcard.sent", "postcard.delivered", "postcard.failed", "postcard.returned"]
}
Response (201 Created)
{
  "id": "wh_abc123",
  "url": "https://example.com/webhooks/postcards",
  "events": ["postcard.created", "postcard.sent", "postcard.delivered", "postcard.failed", "postcard.returned"],
  "secret": "whsec_...",
  "active": true,
  "created_at": "2026-03-09T12:00:00Z"
}

The secret is only returned on creation. Save it securely — use it to verify webhook signatures via HMAC-SHA256 in the X-PostcardBot-Signature header.

GET
/api/v1/webhooks

List all webhooks for your account

Response
{
  "webhooks": [
    {
      "id": "wh_abc123",
      "url": "https://example.com/webhooks/postcards",
      "events": ["postcard.sent", "postcard.delivered"],
      "active": true,
      "created_at": "2026-03-09T12:00:00Z"
    }
  ]
}
PUT
/api/v1/webhooks/:id

Update a webhook (URL, events, or active status)

Request body
{
  "url": "https://example.com/new-endpoint",
  "events": ["postcard.sent"],
  "active": false
}
DELETE
/api/v1/webhooks/:id

Delete a webhook

Webhook Payload

Events are sent as POST requests with HMAC-SHA256 signature in the X-PostcardBot-Signature header (format: t=timestamp,v1=base64signature).

Example payload
{
  "event": "postcard.sent",
  "postcard_id": "pc_abc123",
  "status": "sent",
  "to": {
    "name": "Jane Doe",
    "city": "San Francisco",
    "country": "US"
  },
  "timestamp": "2026-03-09T12:01:00Z"
}

Webhooks that return 410 Gone are automatically deactivated.

Error Responses

401

Unauthorized

Missing or invalid API key

402

Insufficient balance

Top up at postcard.bot/buy-credits

400

Validation error

Missing or invalid fields (details in response body)

Code Examples
Send real postcards from code in any language

curl

Send a postcard
curl -X POST https://postcard.bot/api/v1/postcards \
  -H "Authorization: Bearer pk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "to": {
      "name": "Jane Doe",
      "address_line1": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94102",
      "country": "US"
    },
    "from": {
      "name": "John Smith",
      "address_line1": "456 Oak Ave",
      "city": "New York",
      "state": "NY",
      "zip": "10001"
    },
    "message": "Wish you were here!",
    "image_url": "https://example.com/photo.jpg"
  }'

Python

send_postcard.py
import requests

response = requests.post(
    "https://postcard.bot/api/v1/postcards",
    headers={"Authorization": "Bearer pk_live_your_key_here"},
    json={
        "to": {
            "name": "Jane Doe",
            "address_line1": "123 Main St",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94102",
            "country": "US",
        },
        "from": {
            "name": "John Smith",
            "address_line1": "456 Oak Ave",
            "city": "New York",
            "state": "NY",
            "zip": "10001",
        },
        "message": "Wish you were here!",
        "image_url": "https://example.com/photo.jpg",
    },
)

result = response.json()
print(f"Postcard {result['id']} sent! Delivery: {result['expected_delivery_date']}")

Node.js / TypeScript

send-postcard.ts
const response = await fetch("https://postcard.bot/api/v1/postcards", {
  method: "POST",
  headers: {
    "Authorization": "Bearer pk_live_your_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: {
      name: "Jane Doe",
      address_line1: "123 Main St",
      city: "San Francisco",
      state: "CA",
      zip: "94102",
      country: "US",
    },
    from: {
      name: "John Smith",
      address_line1: "456 Oak Ave",
      city: "New York",
      state: "NY",
      zip: "10001",
    },
    message: "Wish you were here!",
    image_url: "https://example.com/photo.jpg",
  }),
});

const result = await response.json();
console.log(`Postcard ${result.id} sent! Delivery: ${result.expected_delivery_date}`);

Full API spec available at /api/v1/openapi (OpenAPI 3.0 JSON). Import into Postman, Swagger UI, or any API client.

Pricing
Prepaid balance. Volume discounts based on lifetime top-up progress.
TierLifetime top-upUSAInternational
Pay-as-you-go$0$1.99$2.99
Starter$1-$19$1.49$2.49
Bronze$20-$49$1.29$2.29
Silver$50-$199$0.99$2.19
Gold$200-$499$0.85$2.09
Platinum$500-$999$0.79$1.99
Diamond$1,000+$0.69$1.89

Pricing is based on your lifetime top-up progress. Your tier upgrades automatically as you add more credits. Prepaid balance required - add credits via postcard.bot/buy-credits or the API. Auto-recharge coming soon.

Lifetime top-up progress does not expire. Per-card prices may change over time. Current USD rates. Prices may change over time due to postage, printing, inflation, taxes, currency, or operating cost changes. The applicable price is shown before checkout or before an API send deducts balance.

Environment Variables
VariableRequiredDescription
POSTCARDBOT_API_KEYYesYour API key from postcard.bot/account
POSTCARDBOT_API_URLNoOverride API base URL (default: https://postcard.bot)

Get API Key · Buy Credits · npm Package · MCP Server · Code Examples · OpenAPI Spec