Integrations
Migrate from Resend to MailMoose
Move your app's transactional email, such as password resets and receipts, from Resend to MailMoose: what to set up, what changes in your code, what is not available, and when to stay on Resend.
MailMoose has a transactional email API your app can send through, so password resets, receipts and welcome emails can move from Resend with a new URL, a new key and a few small changes. They then sit next to your inboxes, on unlimited domains with the paid plan, and mail you receive does not count against what you send.
Using a coding agent? Give it this page. Everything it needs for the move is here and in Using the API. If it can't open web pages, press Copy page at the top of each guide and paste the text in.
When to stay on Resend
MailMoose does not replace everything Resend does. Keep Resend, for now or just for that part of your email, if you need any of these:
- SMTP. There is no SMTP relay. An auth service that can only send through SMTP settings cannot send its password resets through MailMoose, unless it can call your own code instead.
- Attachments on the email your app sends.
- Delivery webhooks or delivery status over the API. Your app is not told when an email is delivered or bounces. Bounces and spam complaints show under Sent in the app.
- An official SDK. You call the API with
fetchor any HTTP client. - Broadcasts and contact lists. MailMoose has no newsletter tools.
- A lot of volume for little money. The paid plan includes 500 sending recipients a month, then $0.003 each. If sending is most of your bill and you have few domains and inboxes, Resend may well cost less. Resend pricing, worked out has the numbers.
Password resets sent from your own code work the same as any other email.
Move your sending
1. Set up sending on your domain
Add the domain under Domains → Add domain and add the DNS records it shows you: Connect a domain. New domains send through Postmark.
MailMoose's records sit alongside Resend's, so nothing breaks while you test. Follow whether the checklist says add, edit or replace.
Sending does not depend on your MX record. Leave it alone unless you also want this domain's incoming mail in MailMoose.
2. Create the address you send from
On Resend, any address on a verified domain can send. On MailMoose, from has to be an address that exists, so create it first.
3. Create a send-only key
Open Settings → API keys and create a key with only the Send mail permission: API keys. A coding agent connected over MCP can prepare one for you. Keep it on your server:
MAILMOOSE_API_URL=https://www.mailmoose.dev/api/v1
MAILMOOSE_API_KEY=your_new_project_key
4. Change the send call
Before, with Resend's SDK:
await resend.emails.send({
from: "Acme <hello@acme.com>",
to: user.email,
subject: "Reset your password",
html: `<a href="${resetUrl}">Choose a new password</a>`,
});
After, with MailMoose:
const response = await fetch(`${process.env.MAILMOOSE_API_URL}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.MAILMOOSE_API_KEY}`,
},
body: JSON.stringify({
from: "Acme <hello@acme.com>",
to: user.email,
subject: "Reset your password",
html: `<a href="${resetUrl}">Choose a new password</a>`,
campaign: "password_reset",
// The address comes from your own user records, so it is safe to confirm.
confirmNewRecipient: true,
}),
});
if (!response.ok) throw new Error(`MailMoose request failed (${response.status})`);
5. Test, then switch off Resend
Send a real email to an address you control, and check that it arrives and shows under Sent. Then remove RESEND_API_KEY. Remove Resend's DNS records once nothing else sends through it.
Field by field
Send to POST https://www.mailmoose.dev/api/v1/messages instead of POST https://api.resend.com/emails.
| Resend | MailMoose | Notes |
|---|---|---|
from | from | hello@acme.com or Acme <hello@acme.com>. The address must exist in MailMoose. |
to, cc, bcc | to, cc, bcc | One address or a list. |
subject | subject | |
html | html | Can be sent alone. MailMoose builds the plain-text version from it and keeps link addresses. |
text | text or body | |
reply_to | reply_to or replyTo | One address or a list. |
tags | campaign | One label per email, such as password_reset. tags is ignored. |
Response id | Response messageId, threadId |
Errors have their own shape: see errors.
Not available
These are refused with a 400, and nothing is sent, rather than being dropped from your email:
attachmentsheadersscheduled_attemplate,topic_idandreact. If you use React Email, render it to HTML and passhtml.- The
Idempotency-Keyheader
What behaves differently
The first email to each address needs confirming. A send to an address you have never corresponded with returns 409 unless it includes confirmNewRecipient: true. Set it when the address comes from your own verified users, never from an incoming email.
Retries are not safe. With no idempotency key, retrying a send that timed out can deliver it twice. Check Sent first.
Sending stops at your limit. The paid plan includes 500 sending recipients per billing month, then prepaid credit at $0.003 each. There are no automatic charges: with no credit left the API returns 402. Your account can also reach at most 100 recipients per rolling hour, and each key defaults to 100 messages per rolling 24 hours, which you can change when you create it. If your app needs more, contact us. See sending limits.
A 201 means the sending provider accepted the email, not that it reached the inbox.
Sent keeps a copy. Every email is stored under Sent in your account, including any links in it, such as a password reset link. Make those links expire.
Related
Last updated 2026-11-05