Sign in Create account
Getting started

API documentation

Send and receive email, manage domains and follow every delivery with a simple REST API, with the option to process your email in São Paulo.

#Overview

The Postal API is a REST API with JSON. You send transactional emails, receive messages in your inboxes, manage domains, templates, webhooks and the suppression list, take care of the marketing audience (contacts, segments, topics and sign-up forms), send campaigns, build automations from your product events and query logs and metrics, all with one API key. If you prefer SMTP, use the same key at smtp.postal.so.

#Base URL

Every request goes to:

Base URL
https://api.postal.so

Always use HTTPS. Request and response bodies are JSON (Content-Type: application/json), in UTF-8. The exceptions are image uploads, in multipart/form-data, and the original messages (/raw), in plain text.

#Your first request

With a key in POSTAL_API_KEY and a verified domain, sending is just this:

POST /emails

cURL

curl -X POST https://api.postal.so/emails \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme Store <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Your order #10482 is out for delivery",
    "html": "<p>Order <b>#10482</b> is on its way.</p>"
  }'

Node.js

const response = await fetch('https://api.postal.so/emails', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.POSTAL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: 'Acme Store <[email protected]>',
    to: ['[email protected]'],
    subject: 'Your order #10482 is out for delivery',
    html: '<p>Order <b>#10482</b> is on its way.</p>',
  }),
});

const data = await response.json();

Python

import os
import requests

response = requests.post(
    "https://api.postal.so/emails",
    headers={"Authorization": f"Bearer {os.environ['POSTAL_API_KEY']}"},
    json={
        "from": "Acme Store <[email protected]>",
        "to": ["[email protected]"],
        "subject": "Your order #10482 is out for delivery",
        "html": "<p>Order <b>#10482</b> is on its way.</p>",
    },
    timeout=10,
)

data = response.json()

PHP

$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.postal.so']);

$response = $client->post('emails', [
    'headers' => ['Authorization' => 'Bearer '.getenv('POSTAL_API_KEY')],
    'json' => [
        'from' => 'Acme Store <[email protected]>',
        'to' => ['[email protected]'],
        'subject' => 'Your order #10482 is out for delivery',
        'html' => '<p>Order <b>#10482</b> is on its way.</p>',
    ],
]);

$data = json_decode($response->getBody(), true);
200Response
{
  "id": "019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b"
}

The full walkthrough, with the domain and key setup, is in Getting started.

#Conventions

  • IDs are time-ordered UUIDs, like 019a1b2c-4d5e-7f60-8a9b-0c1d2e3f4a5b.
  • Dates come in ISO 8601, in UTC (2026-10-24T13:02:11.000000Z). In the ones you send, include the time zone, like 2026-10-25T09:00:00-03:00.
  • Objects carry an object field (email, domain, list…) that says what the response is.
  • Lists use cursor pagination, with has_more.
  • Deletions answer {"object": "…", "id": "…", "deleted": true}.
  • Errors always follow the {"statusCode", "name", "message"} format. See Errors.
  • Field and error names are in English, to make searching and integrating easier. The error message comes in English, or in Portuguese with Accept-Language: pt-BR.

#API resources

The reference is grouped by resource. Each endpoint has its parameters, examples in cURL, Node.js, Python and PHP, the response and the possible errors.

#Data retention

Sent emails (with content, attachments and events), webhook deliveries and API logs are kept for 30 days. After that they are deleted automatically. If you need a longer history, keep the events you receive through webhooks or use the exports.