New: campaigns, automations and product emails in the same account. Meet Postal
Sign in Create account

For developers

Send email from your code, the way you already do.

REST API with the same formats as Resend, SMTP, SDKs for Node.js, PHP and Laravel, and a CLI. To migrate, you can usually just swap the base URL and the key.

Resend-compatible API
REST
Ports 465 and 587
SMTP
SDKs: Node.js and PHP
2
event types in webhooks
30
1

Create a key

Use one key per system and per environment. The checkout, WordPress or the ERP only need to send: give them a send-only key, tied to the right domain.

  • If a send-only key leaks, it can only send and see what it sent itself
  • The key is shown only once, at creation
  • Send-only keys tied to a domain depend on the plan
2

Send the first email

All paths go through the same rules: verified domain, quotas, suppression list and the same events. Choose where to send from.

The API you already know

One POST to /emails and the email goes out. Field names and response and error formats are Resend's, so what you already know still applies.

  • Batch sending, up to 100 emails per request
  • Scheduling up to 30 days ahead, and cancellation (batch and scheduling depend on the plan)
  • Idempotency-Key to repeat a call without duplicating the send
See the emails reference
curl
curl -X POST https://api.postal.so/emails \
  -H "Authorization: Bearer $POSTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Aurora Cosméticos <[email protected]>",
    "to": ["[email protected]"],
    "subject": "Your order #10482 is out for delivery",
    "html": "<p>Order <b>#10482</b> is on its way.</p>"
  }'
200Response: {"id": "019a1b2c-..."}

SDK for Node.js and TypeScript

The postal-so package has types, typed errors, retry with exponential backoff and webhook signature verification. It works on Node 18 or newer, with no dependencies.

  • npm install postal-so
  • Reads the key from POSTAL_API_KEY
  • Includes the postal CLI for testing from the terminal
See the SDKs
Node.js
import { Postal } from 'postal-so';

const postal = new Postal(); // reads POSTAL_API_KEY

const { id } = await postal.emails.send(
  {
    from: 'Aurora Cosméticos <[email protected]>',
    to: '[email protected]',
    subject: 'Your order #10482 is out for delivery',
    html: '<p>Order <b>#10482</b> is on its way.</p>',
  },
  { idempotencyKey: 'order-10482-shipped' },
);

SDK for PHP and transport for Laravel

The postal-so/postal-php package requires PHP 8.2 or newer. In Laravel, it creates the client on its own and ships the postal transport: just declare the mailer in config/mail.php and use MAIL_MAILER=postal, and Mail::to() stays the same.

  • composer require postal-so/postal-php
  • Mailables, notifications, attachments and tags keep working
  • Prefer SMTP? Laravel also works with Postal's SMTP server
See the Laravel guide
PHP
use Postal\Client;

$postal = new Client(); // reads POSTAL_API_KEY

$result = $postal->emails->send([
    'from' => 'Aurora Cosméticos <[email protected]>',
    'to' => '[email protected]',
    'subject' => 'Your order #10482 is out for delivery',
    'html' => '<p>Order <b>#10482</b> is on its way.</p>',
], 'order-10482-shipped'); // idempotency key

echo $result['id'];

SMTP for whatever doesn't speak API

WordPress, ERPs, store platforms and printers send through Postal's SMTP server, with an API key as the password. The same API rules apply: verified domain, quotas and suppression list. SMTP depends on the plan.

  • Port 465 (recommended) or 587; 2465 and 2587 if your network blocks them
  • The response code carries the email id
  • Up to 40 MB and 50 recipients per message
See the SMTP setup
SMTP setup
Server     smtp.postal.so
Port       465 (direct TLS) or 587 (STARTTLS)
User       postal
Password   an API key (ps_...)

# Laravel, in .env
MAIL_MAILER=smtp
MAIL_SCHEME=smtps
MAIL_HOST=smtp.postal.so
MAIL_PORT=465
MAIL_USERNAME=postal
MAIL_PASSWORD=ps_your_key
3

Follow along in the logs

Every request made with an API key and every message sent over SMTP appears under Logs, with the body sent and the response. Logs are kept for 30 days.

4

Receive the events

Receive every email event on your server, with a Standard Webhooks signature, up to 8 attempts and manual resend. The SDK verifies the signature in one line.

  • 30 event types, from emails to contacts and inboxes
  • Same webhook-id on every attempt, so you can discard duplicates
  • Test event and test addresses, such as [email protected]
Node.js, with Express
import express from 'express';
import {
  verifyWebhook,
  PostalWebhookVerificationError,
} from 'postal-so';

const app = express();

// The body must arrive raw (Buffer)
app.post(
  '/webhooks/postal',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    try {
      const event = verifyWebhook(
        req.body,
        req.headers,
        process.env.POSTAL_WEBHOOK_SECRET,
      );
      if (event.type === 'email.bounced') {
        // remove the address
      }
      res.sendStatus(200);
    } catch (error) {
      if (error instanceof
          PostalWebhookVerificationError) {
        return res.sendStatus(400);
      }
      throw error;
    }
  },
);

See the webhooks page

5

Test without really sending

Test addresses generate real events and webhooks, don't use the quota and don't affect your reputation. They depend on the plan. The CLI helps you test from the terminal, with JSON output for scripts.

Test addressEvents it generates
[email protected]email.sent and email.delivered
[email protected]email.sent and email.bounced, with a permanent bounce
[email protected]email.sent, email.delivered and email.complained
[email protected]Only email.suppressed
[email protected]email.sent and email.delivery_delayed
npm install --global postal-so
postal login
postal emails send \
  --from [email protected] \
  --to [email protected] \
  --subject "Teste" --text "Funcionou"
6

Migrate from Resend

Postal's API uses the same features, field names and response formats, so migrating usually means swapping the base URL and the key. The Resend SDK keeps working if you point the base URL to Postal.

  • Tested with the resend 6.34 package for Node.js
  • What changes: the secret and signing rules for webhooks, limits and quotas
Before and after
- curl -X POST https://api.resend.com/emails \-   -H "Authorization: Bearer re_..."- - # ou, com o SDK da Resend, no .env- RESEND_BASE_URL=https://api.resend.com+ curl -X POST https://api.postal.so/emails \+   -H "Authorization: Bearer ps_..."+ + # ou, com o SDK da Resend, no .env+ RESEND_BASE_URL=https://api.postal.so
ItemOn Postal
Base URL and keyhttps://api.postal.so and Authorization: Bearer ps_…
Sending and errorsPOST /emails returns {"id"} and errors follow {statusCode, name, message}. Same.
Event namesThe same ones (email.delivered, email.bounced…), plus others, such as email.replied and contact.*.
WebhooksStandard Webhooks format: webhook-* and svix-* headers. The secret (whsec_…) belongs to each endpoint.
Test addresses[email protected] becomes [email protected], and so on.
Limits and quotasPer team and per plan. See Limits.

Before you flip the key

  • Domain verified on Postal (you can keep the same one on both platforms at the same time)
  • API key created: send-only and tied to the domain, for anything that only sends
  • Suppression list imported, 100 addresses per request
  • Webhook created and signature verified before you switch traffic
  • Base URL and key swapped first in the test environment

Read the migration guide

7

Codes the app fills in

The OTP-Token header carries the code and the originating site. It is still an IETF draft and not every email client reads it; those that don't know it ignore it.

From: Aurora Cosméticos <[email protected]>
Subject: Your access code
OTP-Token: "123456"; origin="https://app.exemplo.com.br"

Documentation

Everything in the documentation

Guides, the complete API reference and examples in cURL, Node.js, Python and PHP.

Frequently asked questions

Can I keep using the Resend SDK?

Yes. Point the base URL to https://api.postal.so and use the Postal key. We tested the resend 6.34 package for Node.js; other SDKs that let you change the URL speak the same format, but have not been tested one by one.

When should I use SMTP and when the API?

Use SMTP in systems that don't talk to APIs, such as WordPress and ERPs. Use the API when you need tags, scheduling or templates, which SMTP doesn't have.

What is the request limit?

It applies per team and per second, and depends on the plan. If you go over the limit, the response is 429, with the Retry-After header. The SDKs wait and retry on their own. See Limits.

How do I test without sending a real email?

Send to the test addresses, such as [email protected], if your plan includes them. The email is simulated, doesn't use the quota and generates real events and webhooks.

Want to try Postal?

Create your account and start on the Free plan.