Migrate from Resend
letr.dev as a Resend alternative, with API keys, DNS records, webhook events, SMTP settings and suppressions mapped for the move.
For an app that sends its sign-in codes, receipts and password links through Resend. letr.dev takes over that part. It sends no newsletters or campaigns and stores no templates.
letr.dev costs CHF 12 a month for 10'000 recipients, then CHF 0.50 per started 1'000 recipients, up to a monthly ceiling of 10'000 or 25'000 recipients that you choose; for more, letr.dev can grant a higher ceiling (limits). letr.dev counts per recipient: an email to three people counts three (overage).
Steps
- Sign up. The account goes live on payment and starts on step 0 with 50 recipients a day (daily limit).
- Add your sending domain and publish letr.dev's DKIM record next to Resend's records.
- Create one API key per app.
- Create a webhook endpoint for the events you use.
- Import your Resend suppressions.
- Point the app at letr.dev's API or SMTP relay.
- Remove Resend's records once Resend sends nothing more from the domain.
API keys
| Resend | letr.dev |
|---|---|
full_access |
sending_access, the least privilege an app that sends needs; full_access only for the one-off suppression import |
sending_access, optionally limited to a domain |
sending_access, optionally limited to some of your domains |
| None | read_only: every GET under /v1 |
Source: Resend's create API key reference, 01.11.2026; letr.dev's permissions.
DNS records
letr.dev needs one TXT record for DKIM at letr._domainkey, and advises DMARC. Bounces return to bounces.letr.dev, so your SPF record stays as it is (DNS records). Resend's records sit on the return-path subdomain, send by default; names below are relative to your domain.
| Resend record | With letr.dev |
|---|---|
MX send, to feedback-smtp.us-east-1.amazonses.com (the region part varies) |
Not needed |
TXT send, v=spf1 include:amazonses.com ~all |
Not needed |
CNAME records on the return-path subdomain and its r sibling, where Resend shows CNAMEs instead |
Not needed |
TXT resend._domainkey (DKIM) |
Replaced by TXT letr._domainkey |
TXT _dmarc |
Keep it: a record that starts with v=DMARC1 counts |
Because the selectors differ, both DKIM records can stay side by side during the switch. Remove Resend's once its last emails are out. Source: Resend's add a domain, Cloudflare and custom return path guides, 01.11.2026.
Webhook events
Most event names are the same.
| Resend event | letr.dev event |
|---|---|
email.sent |
email.sent, when letr.dev's mail server accepted the email for delivery; at Resend, when the API request succeeded |
email.delivered |
email.delivered |
email.delivery_delayed |
email.deferred |
email.bounced |
email.bounced |
email.complained |
email.complained |
email.failed |
email.failed; key, domain and quota errors letr.dev answers at once in the API response instead |
email.suppressed |
None: letr.dev refuses at once to send to a suppressed address, with status 422 |
email.opened, email.clicked |
None: letr.dev does not track opens or clicks |
email.scheduled |
None: letr.dev sends scheduled emails without an event for scheduling |
email.received, suppression.*, contact.*, domain.* and topic events |
None |
letr.dev signs every event with the endpoint's own secret; verify the signature before you trust it. Source: Resend's event types, 01.11.2026.
SMTP
Resend uses the host smtp.resend.com on port 465 or 2465 with implicit TLS, or 25, 587 or 2587 with STARTTLS, the username resend and an API key. letr.dev uses:
| Setting | Value |
|---|---|
| Host | smtp.letr.dev |
| Port | 465 with implicit TLS or 587 with STARTTLS (required) |
| Username | letr |
| Password | An API key with sending_access |
The SMTP guide shows the settings for each framework. Source: Resend's SMTP guide, 01.11.2026.
Suppressions
On Resend's Suppressions page, Download CSV exports your suppressions, whether they came from a bounce, a complaint or your own entry (suppressions, 01.11.2026). letr.dev reads only the first column of each line. Make sure the email address is the first column, then create a full_access key in the dashboard only for the import and import the file with it:
curl https://api.letr.dev/v1/suppressions/import \
-H "Authorization: Bearer $LETR_API_KEY" \
-H "Content-Type: text/csv" \
--data-binary @suppressions.csv
An import holds at most 10'000 lines and 1 MB; split larger files. Imported addresses get the reason manual (import a list). When the import is done, revoke that key in the dashboard; revoking stops it at once. The app keeps its sending_access key.
What letr.dev does not do
- Newsletters and campaigns: letr.dev sends transactional email only.
- Templates: your app renders the email and sends the finished
htmlandtext. - Receiving email, contacts and topics.
- Open and click tracking.