Guide
Migrate from Resend
Move from Resend to Emailit. The APIs are close, so changes are small. Map fields, SMTP settings and webhook events, verify signatures and move contacts.
This guide maps Resend concepts, API calls, webhooks and templates to their Emailit equivalents. The two APIs have a similar shape, so most code changes are small. Read Migrate to Emailit first for the overall order and how to run both providers in parallel.
Concepts
| Resend | Emailit |
|---|---|
| Team | Workspace. Each workspace has its own domains, keys, members and credits. |
| API key with full or sending access | API key: Full Access, or Sending Only optionally restricted to one domain |
| Domains | Sending domains |
| Audiences and contacts | Contacts and audiences |
| Broadcasts | Campaigns |
| Webhooks | Webhooks |
| Emails and logs | Email APIEmails and Email APILogs |
tags |
meta |
| React Email components | HTML you render in your code, or templates designed in the dashboard |
Update your API calls
Resend’s POST /emails becomes Emailit’s POST /v2/emails, with almost the same fields. In Node.js:
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'Acme <hello@acme.com>',
to: ['ada@example.com'],
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
});import { Emailit } from '@emailit/node';
const emailit = new Emailit(process.env.EMAILIT_API_KEY);
await emailit.emails.send({
from: 'Acme <hello@acme.com>',
to: ['ada@example.com'],
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
});| Resend | Emailit |
|---|---|
Authorization: Bearer re_… |
Authorization: Bearer secret_… |
from, subject, html, text, headers |
The same |
to, cc, bcc |
The same, up to 50 each |
reply_to (replyTo in the Node.js SDK) |
reply_to |
react (Node.js SDK) |
Not available. Render the component to HTML and pass html. |
attachments[] with filename and content or path |
attachments[] with filename and base64 content or a url. content_type is required with content. |
tags: [{ "name": "…", "value": "…" }] |
meta: { "name": "value" }, returned in webhook events |
scheduled_at |
scheduled_at (ISO 8601, Unix time or plain English) |
Idempotency-Key header |
Idempotency-Key header, kept for 24 hours |
POST /emails/batch |
Not available. Send one request per message. |
Response { "id": "…" } |
200 with id (em_…), status: "accepted", and ids with one ID per recipient |
Each recipient in an Emailit request becomes its own email with its own ID. When you send to several recipients, the response’s ids maps each address to its ID. Emailit also tracks opens and clicks per email with tracking: { "loads": true, "clicks": true }, once the domain has a verified tracking subdomain. See Send an email.
React Email
If you build emails with React Email, keep your components and render them yourself before you send:
import { render } from '@react-email/render';
import Receipt from './emails/receipt';
const html = await render(<Receipt order={order} />);
const text = await render(<Receipt order={order} />, { plainText: true });
await emailit.emails.send({ from, to, subject, html, text });Switch SMTP settings
| Setting | Resend | Emailit |
|---|---|---|
| Host | smtp.resend.com |
smtp.emailit.com |
| Port | 587, 2587 or 25 (STARTTLS), 465 or 2465 (TLS) |
587, 2587, 2525 or 25 (STARTTLS), 465 (TLS) |
| Username | resend |
emailit |
| Password | Your Resend API key | Your Emailit API key |
See SMTP settings.
Map webhook events
| Resend event | Emailit event |
|---|---|
email.sent |
email.accepted (API only) |
email.delivered |
email.delivered |
email.delivery_delayed |
email.attempted |
email.bounced |
email.bounced |
email.complained |
email.complained |
email.opened |
email.loaded |
email.clicked |
email.clicked |
contact.created, contact.updated, contact.deleted |
The same names |
domain.created, domain.updated, domain.deleted |
The same names |
The request format changes:
- Resend posts one event per request. Emailit posts a JSON array of up to 100 events, so loop over the body.
- The email is in
data.object. Usedata.object.id, theem_ID from the send response, instead ofdata.email_id. Yourmetavalues are indata.object.meta. - Signatures work differently. Resend signs webhooks through Svix, with
svix-*headers. Emailit sendsX-Emailit-Signature, the hex HMAC-SHA256 of<timestamp>.<raw body>, keyed with the whole secret including thewhsec_prefix. A Svix verification library won’t work, even though both secrets start withwhsec_. See Request signature.
for (const event of req.body) {
const email = event.data.object;
if (event.type === 'email.bounced') markBounced(email.to, email.id);
if (event.type === 'email.complained') unsubscribe(email.to);
}Move suppressions and contacts
-
Collect every address that bounced or complained while you used Resend, from Resend’s dashboard or from the events your webhooks stored.
-
Build one CSV with the columns
email,type,reason:email,type,reason old-address@example.com,recipient,resend bounce angry@example.com,recipient,resend complaintUse the type
recipientfor addresses that must never receive email. It blocks API, SMTP and campaign sends. -
In Email APISuppressions, select Import and upload the file. Each file can have up to 10,000 rows and can be at most 8 MB. Duplicates are skipped.
If you send broadcasts, export each Resend audience as a CSV and import it in Email MarketingContacts with Import, choosing the matching Emailit audience. Map the unsubscribed column so people who opted out stay opted out. See Import and export contacts.
Move templates
If you render emails in code, with React Email or anything else, nothing changes: send the HTML. If you keep templates in Resend, recreate them in Email MarketingTemplates or with the Templates API, give each one an alias, and send it with template and variables.
Emailit templates use Temple: {{name}}, nested values such as {{user.name}}, defaults such as {{name|"there"}}, and {{#if plan}}…{{else}}…{{/if}}. There are no loops, so render lists in your code. Temple never escapes HTML, so escape user input before you pass it.
Change DNS
Add your domain in Email APIDomains and publish the Emailit records. They use their own names (emailit._domainkey, emailit.<domain>, and optionally go and inbound), so they don’t conflict with Resend’s records, which typically live on resend._domainkey and a send subdomain. Keep your DMARC record. After the cutover, remove the Resend records. See DNS records.
Next steps
- Go-live checklist
- Set up webhooks
- Priority migration: let Emailit engineers do the move with you