Mail A Letter API
Send physical mail with an HTTP request. Upload a PDF, give us the recipient's address,
and we print, stuff, stamp, and mail it. Postage and printing are billed from your prepaid
MailBank balance — no card details ever
touch the API.
Downloads:
OpenAPI 3.0 specification |
Postman collection
Getting started
- Sign in and open your Account page.
- Set Enable API to Yes and save. You get two keys:
a production key (
mal_live_…) that charges your MailBank and mails real letters,
and a sandbox key (mal_test_…) for integration testing.
- Add funds to your MailBank before going live.
Authenticate every request with your key in the Authorization header
(or an X-Api-Key header). Base URL: https://www.mailaletter.com/api/v1
curl https://www.mailaletter.com/api/v1/balance \
-H "Authorization: Bearer mal_test_YOUR_SANDBOX_KEY"
Send a letter
POST /api/v1/letters — the file must be a
PDF (20MB max, US Letter or A4, portrait or landscape), base64-encoded.
Password-protected PDFs are rejected. Recommended: 0.5in margins minimum.
curl https://www.mailaletter.com/api/v1/letters \
-H "Authorization: Bearer mal_test_YOUR_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_name": "welcome.pdf",
"file": "'"$(base64 < welcome.pdf | tr -d '\n')"'",
"recipient": {
"first_name": "Jane",
"last_name": "Doe",
"company": "Acme Corp",
"address1": "500 Union St",
"address2": "Suite 100",
"city": "Seattle",
"state": "WA",
"postal_code": "98101",
"country": "US"
},
"options": {
"return_envelope": false,
"delivery_service": "first_class"
}
}'
| Field | Required | Notes |
file_name | Yes | Must end in .pdf |
file | Yes | Base64-encoded PDF, 20MB max |
recipient.first_name / last_name | Yes* | Combined max 40 characters. *Optional when company is given |
recipient.company | No* | Max 40 characters. *Required when no name is given |
recipient.address1 | Yes | Street address |
recipient.address2 | No | Apartment, suite, unit. Two street lines is the limit — there is no address3 |
recipient.city | Yes | |
recipient.state | US & Canada | 2-letter state for US addresses; province for Canada |
recipient.postal_code | US & Canada | |
recipient.country | Yes | 2-letter ISO code (e.g. US) or full name |
options.return_envelope | No | Include a self-addressed return envelope. Defaults to false |
options.delivery_service | No | One of first_class (default), tracking, certified, restricted_delivery, express. Exactly one service per letter. tracking, certified and restricted_delivery are US destinations only — requesting them for an international address is a 400 |
Successful response (201):
{
"id": "1024042",
"test_mode": false,
"status": "processing",
"pages": 2,
"cost": 1.87,
"tax": 0.00,
"total": 1.87,
"balance_remaining": 48.13
}
Washington state destinations add 9.5% sales tax.
If your MailBank cannot cover the letter you get 402 with your balance and the
amount required; the letter is saved as a draft you can pay for on the website after
topping up. Nothing is mailed until it is paid.
We also email your low-funds address (configurable on the Account page) — at most one
email per day, no matter how many requests are declined.
Track your letters
GET /api/v1/letters/{id} — status is one of
processing, address_verification, printing, printed,
awaiting_delivery, mailed, canceled, errored,
draft, pending_payment.
{
"id": "1024042",
"test_mode": false,
"status": "mailed",
"pages": 2,
"cost": 1.87,
"tax": 0.00,
"total": 1.87,
"fulfillment_provider": "USPS",
"tracking_number": "9407300000000000000001"
}
fulfillment_provider appears once the letter is in the carrier pipeline
(printing onward). tracking_number appears only for letters mailed
with a delivery_service other than first_class — standard
first-class letters do not carry one.
GET /api/v1/balance — your current MailBank balance.
{
"balance": 48.13,
"currency": "USD"
}
Test mode
Use your sandbox key and every call runs the complete pipeline — full PDF validation,
address validation, and real pricing from your rate table — but nothing is stored,
charged, or mailed. Responses have "test_mode": true and ids beginning
test_. Test letters progress automatically so you can build polling:
processing → printing after 5 minutes → mailed
after 30 minutes.
Deterministic failure triggers (sandbox only):
| Trigger | Result |
recipient postal_code = 00000 | 400 invalid_address |
recipient first_name = TEST-INSUFFICIENT-FUNDS | 402 insufficient_funds |
Errors
All errors share one shape:
{ "error": { "code": "insufficient_funds", "message": "...", "balance": 0.42, "required": 1.87 } }
| HTTP | Code | Meaning |
| 400 | invalid_json, invalid_request | Malformed body, missing fields, or an invalid options value |
| 400 | invalid_pdf, encrypted_pdf, empty_pdf, unsupported_page_size | The file failed PDF validation |
| 400 | invalid_address | Recipient address incomplete or country unknown |
| 401 | unauthorized | Missing/unknown key, or API not enabled on the account |
| 402 | insufficient_funds | MailBank balance too low; letter saved as draft |
| 403 | https_required | The API is only available over HTTPS |
| 404 | not_found | No such letter on this account, or unknown API path |
| 405 | method_not_allowed | Wrong HTTP method for the path; see the Allow header |
| 413 | file_too_large | PDF over 20MB |
| 415 | unsupported_media_type | Only PDF is accepted — file_name missing or not ending in .pdf |
| 429 | rate_limited | Over 120 requests/minute; honor Retry-After |
| 500 | internal_error | Our fault — contact support@mailaletter.com |
Questions? Email support@mailaletter.com.