Migrate/Resend
Package + key swap

Switch from Resend to Anypost

If you're coming from Resend, Anypost will feel familiar: both are JSON REST APIs with Bearer auth and a similar set of core send fields. For most apps, migrating means changing your package name, swapping your API key, and adjusting a field or two.

~15 mintypical migration time
2things to adjust (tags, webhooks)
8official SDK languages

What's the same, what changed

Both platforms send authenticated email over a JSON REST API and report the same delivery lifecycle. The core request looks similar; the differences are a handful of details, listed below.

ConceptResendAnypost
Base URLapi.resend.comapi.anypost.com/v1
Auth headerAuthorization: Bearer re_…Authorization: Bearer ap_…
Key prefixre_ap_
Send endpointPOST /emailsPOST /v1/email
Batch endpointPOST /emails/batchPOST /v1/email/batch
Response ididid
from, to, cc, bccSame namesSame names
reply_toSame nameSame name
html / textSame namesSame names
AttachmentsBase64 in JSONBase64 in JSON
IdempotencyIdempotency-Key headerIdempotency-Key header
Tags[{name, value}] objects["string"] (array of strings)
Templatesreact-email / HTML stringMarkdown or HTML, stored server-side
Webhooksemail.* events, one per requestemail.* events, batched array
SMTP hostsmtp.resend.comsmtp.anypost.com
SMTP port465 (implicit TLS)587 (STARTTLS)

The one field to watch: Resend's tags take an array of { name, value } objects. Anypost's tags take plain strings, like ["welcome", "onboarding"] (up to 10). Flatten your tag objects into strings and the rest of the body carries over. See Tags, topics & campaigns.

Four steps to switch

You can run Resend and Anypost in parallel during cutover; the two domains are independent. Migrate one email type at a time and verify delivery before cutting over completely.

Create an Anypost account and verify your domain

Sign up at anypost.com and add the same sending domain you use with Resend. You'll publish DNS records (an SPF, a DKIM, and a tracking CNAME), then click verify. Propagation takes a few minutes to a few hours. Your Resend records don't conflict with Anypost's, so both can be live at once. See Domains.

Create an API key

In the dashboard, create a key (it starts with ap_). Use a send-only key for your application server, just as you would for a Resend key. Set it as ANYPOST_API_KEY alongside your existing RESEND_API_KEY during cutover. See Authentication.

Swap the package and key

Install the Anypost SDK, replace your Resend import, and point the key env var at your new ap_ key. The send call keeps a similar shape.

Before (Resend)
import { Resend } from "resend";
 
const resend = new Resend(process.env.RESEND_API_KEY);
 
await resend.emails.send({
  from: "[email protected]",
  to: ["[email protected]"],
  subject: "Hello",
  html: "<p>Hi there.</p>",
});
After (Anypost)
import { Anypost } from "anypost";
 
const anypost = new Anypost(process.env.ANYPOST_API_KEY);
 
await anypost.email.send({
  from: "[email protected]",
  to: ["[email protected]"],
  subject: "Hello",
  html: "<p>Hi there.</p>",
});

Update your webhook endpoint (if you use one)

Register your existing webhook URL in the Anypost dashboard. The email.* event names carry over, so your switch statement needs only minor changes; you wrap it in a loop because Anypost batches events into an array. See the Webhooks section below.

The same call in every SDK

The change is consistent across languages: swap the package, construct the client with your ap_ key, and call email.send. Pick your language.

Resend
curl https://api.resend.com/emails \
  -H "Authorization: Bearer re_…" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": ["[email protected]"],
    "subject": "Welcome",
    "html": "<p>Hi.</p>"
  }'
Anypost
curl https://api.anypost.com/v1/email \
  -H "Authorization: Bearer ap_…" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": ["[email protected]"],
    "subject": "Welcome",
    "html": "<p>Hi.</p>"
  }'

Webhooks

The email.* event names you're already handling carry over, so your case labels are unchanged. Two things change: Anypost delivers events in a batched events array, and the recipient address is data.recipient rather than data.to.

Before (Resend)
// Resend posts one event per request
app.post("/webhook/email", (req, res) => {
  const { type, data } = req.body;
  switch (type) {
    case "email.delivered":  markDelivered(data.email_id); break;
    case "email.bounced":    suppressAddress(data.to); break;
    case "email.complained": unsubscribe(data.to); break;
    case "email.opened":
    case "email.clicked":    recordEngagement(data); break;
  }
  res.sendStatus(200);
});
After (Anypost)
// Anypost batches events; the recipient is data.recipient
app.post("/webhook/email", (req, res) => {
  for (const { type, data } of req.body.events) {
    switch (type) {
      case "email.delivered":  markDelivered(data.email_id); break;
      case "email.bounced":    suppressAddress(data.recipient); break;
      case "email.complained": unsubscribe(data.recipient); break;
      case "email.opened":
      case "email.clicked":    recordEngagement(data); break;
    }
  }
  res.sendStatus(200);
});

Signature verification differs. Resend signs webhooks with Svix headers; Anypost signs with an Anypost-Signature header (t=<unix>,v1=<hmac>) over the timestamp and raw body. Swap your verification step for Anypost's before trusting a delivery. See Webhooks.

Register your webhook URL once in the Anypost dashboard and select which events to receive. One URL covers every event for your account.

Using SMTP instead of the API?

Point your SMTP client at Anypost's host with the username anypost and your ap_ key as the password. The one real change is the port: Resend uses implicit TLS on 465; Anypost uses STARTTLS on 587.

Resend SMTP
SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_USER=resend
SMTP_PASS=re_…
SMTP_TLS=true
Anypost SMTP
SMTP_HOST=smtp.anypost.com
SMTP_PORT=587
SMTP_USER=anypost
SMTP_PASS=ap_…
SMTP_TLS=true

Anypost supports ports 587 (recommended), 2587, and 25. STARTTLS is required on every port, and there's no implicit-TLS port, so move off Resend's 465 to 587. See Sending over SMTP.

Ready to switch?

Create your account, verify your domain, and send your first email in minutes.