# 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](/docs/migrate/) first for the overall order and how to run both providers in parallel.

## Concepts

| Resend | Emailit |
| --- | --- |
| Team | [Workspace](/docs/workspaces/). Each workspace has its own domains, keys, members and credits. |
| API key with full or sending access | [API key](/docs/developers/api-keys/): **Full Access**, or **Sending Only** optionally restricted to one domain |
| Domains | [Sending domains](/docs/domains/) |
| Audiences and contacts | [Contacts](/docs/contacts/) and [audiences](/docs/audiences/) |
| Broadcasts | [Campaigns](/docs/campaigns/) |
| Webhooks | [Webhooks](/docs/webhooks/) |
| Emails and logs | **Email API → Emails** and **Email API → Logs** |
| `tags` | `meta` |
| React Email components | HTML you render in your code, or [templates](/docs/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:

```javascript title="Before: 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>',
});
```

```javascript title="After: Emailit"

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](/docs/email-api/send-email/).

### React Email

If you build emails with React Email, keep your components and render them yourself before you send:

```jsx

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](/docs/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`. Use `data.object.id`, the `em_` ID from the send response, instead of `data.email_id`. Your `meta` values are in `data.object.meta`.
- Signatures work differently. Resend signs webhooks through Svix, with `svix-*` headers. Emailit sends `X-Emailit-Signature`, the hex HMAC-SHA256 of `<timestamp>.<raw body>`, keyed with the whole secret including the `whsec_` prefix. A Svix verification library won't work, even though both secrets start with `whsec_`. See [Request signature](/docs/webhooks/request-signature/).

```javascript
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

1. Collect every address that bounced or complained while you used Resend, from Resend's dashboard or from the events your webhooks stored.

2. Build one CSV with the columns `email,type,reason`:

```csv
email,type,reason
old-address@example.com,recipient,resend bounce
angry@example.com,recipient,resend complaint
```

   Use the type `recipient` for addresses that must never receive email. It blocks API, SMTP and campaign sends.

3. In **Email API → Suppressions**, 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 Marketing → Contacts** with **Import**, choosing the matching Emailit audience. Map the unsubscribed column so people who opted out stay opted out. See [Import and export contacts](/docs/contacts/import-export/).

## 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 Marketing → Templates** or with the [Templates API](/docs/api-reference/templates/create/), give each one an alias, and send it with `template` and `variables`.

Emailit templates use [Temple](/docs/templates/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 API → Domains** 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](/docs/domains/dns-records/).

## Next steps

- [Go-live checklist](/docs/get-started/go-live/)
- [Set up webhooks](/docs/webhooks/set-up/)
- [Priority migration](/docs/programs/priority-migration/): let Emailit engineers do the move with you

---
Source: https://emailit.com/docs/migrate/resend/
