Replacing Mailgun with Resend

Switching from Mailgun to Resend in the default ShipFast template

This is a side step from my ongoing ShipFast tutorials, I’ll return to the step by step setup next week.

TL;DR

  • Setup a Resend account and get an API key.

  • Update .env.local RESEND_API_KEY and EMAIL_SERVER

  • Update mail settings in config.js

  • Validate your domain name in Resend

  • (optional) Add the Resend node package

  • (optional) Add my library code for using the Resend API to send emails

Mailgun ➡️ flex plan $0 / month for 1,000 emails sent ⬆️ foundation plan $35 / month for 50,000 emails

Resend ➡️ free plan $0 / month for 3,000 emails sent ⬆️ pro plan $20 / month for 50,000 emails

Thanks to salo423 in Discord for the following links if you are using Supabase

Why Change?

The default ShipFast template has an integration with Mailgun. This integration allows for two things: sending emails (like magic links) and forwarding emails to another email account.

It does this by asking you to setup MX records on a subdomain to handle the outgoing and incoming mail. This is why you’ll see the prefix “mg” in the config.js settings for email.

This works well, however I’ve had 2 issues with it.

I don’t like having users see an email address with “mg” in it. I could work around this by setting the config.js to different email addresses, but NextAuth is always going to send the magic links from a “mg” email address.

The second problem is with the routing in Mailgun. On the pay as you go plan there is a limit of 5 routes per account. Since I plan on releasing many Small Bets, I hit this limit yesterday.

There are 2 ways around this issue. I could create another Mailgun account and put 5 more domains under that account. Or I could upgrade my account to the Foundation Plan for $35/month.

Both of these issues gave me the incentive to look for other options. Fortunately Marc mentioned Resend in the ShipFast community.

Switching to Resend

Initially I was a little worried that switching to Resend would require a lot of extra work because the template uses Mailgun by default. It turned out to be very little work.

You’ll need a valid email address on your domain to start with. This can be a full email account setup on your domain (PorkBun my registrar of choice charges $24 / year) or you can forward a domain email to another account (at PorkBun this is free).

Head over the Resend and signup for an account using an email address on your domain. After receiving the magic link in your email and clicking the link, you will see the overview tab.

Adding an API Key

Resend will then ask you to create an API key. Click the button and then copy the generated key into your .env.local file. For consistency I use “RESEND_API_KEY” for the name.

RESEND_API_KEY=[Your API key here without the brackets]

Back on the Resend Overview tab, click the “Send Email” button to confirm everything is working.

SMTP Server

NextAuth uses a package called “nodemailer” to send magic links and “nodemailer” in turn looks for a default environment variable called “EMAIL_SERVER” to use for sending emails.

If you’ve setup Mailgun before this environment variable will be set to the Mailgun SMTP server. We will change that to the Resend SMTP server.

EMAIL_SERVER=smtp://resend:[Your API key here without the brackets]@smtp.resend.com:587

There is no password to generate as was the case with Mailgun, just put your API key generated about in between “:” and “@”.

Email addresses

Next we’ll update config.js to use different email addresses. There are 5 entries under “mailgun” in “config.js” that we will update.

subdomain: ""

We’ll leave this empty since Resend does not use a subdomain to send emails. This alleviates my first issue with Mailgun.

fromNoReply: "ShipFast Tutorial <[email protected]>"

We can put any email address in for this, even one that doesn’t exist. This is what your user will see as the “from” address when they receive a magic link email.

I typically use the “noreply” address. If users reply to the magic link email you won’t receive it unless this is a valid email address and I don’t want to see these “noreply” emails.

The key here is removing the default “mg” subdomain and using your plain domain name.

fromAdmin: "Bill at ShipFast Tutorial <[email protected]>"

This email address will be used when our application sends any emails other than magic links. It’s used in the “sendEmail” helper function in Mailgun.js (which we will replace with Resend a bit later).

supportEmail: "[email protected]"

This should be your support email. I keep this the same as the main email address for simplicity since I am the only one working on my projects. It’s used in the “ButtonSupport.js” component and the “Footer.js” component.

forwardRepliesTo: "[email protected]"

I set this to the same email address as above, but for Resend we won’t actually use this value. Resend does not receive or forward emails, it only sends them.

That’s it for config.js changes!

Validate domain at Resend

Next we’ll head back to Resend and validate our domain name. This will involve adding 3 DNS records at our registrar. Get started by selecting the “Domains” tab and clicking the “Add domain” button.

Adding your domain

Enter your domain name without any subdomain prefixes.

Our plain domain name

You will then see instructions to add 3 DNS entries at your registrar.

3 DNS entries to add

Hop over to PorkBun and open up the DNS entries for your domain. If you have the original 6 from Mailgun, go ahead and delete those. Then add the 3 from Resend.

It’s important to copy and paste the DNS information from Resend to Porkbun as is. There is no “mg” subdomain or any other subdomain that you need to add.

You’ll see that Resend is having you setup an MX record on a subdomain called “send”. This allows Resend to send emails through your domain and your existing MX records to continue receiving emails on your domain. This is different from the way Mailgun is setup.

This is why we use email addresses on the naked domain name without the need for "mg”.

Verify your DNS records

When you are finished at Porkbun, head back to Resend and hit the “Verify DNS Records” button. It may take a few minutes, but each DNS line will go from “Pending” to “Verified” and turn green. If you don’t see this after a few minutes, go back and make sure your DNS entries are correct.

We now have everything setup and it’s time to start testing.

Testing locally

Open the terminal in VS Code and type “npm run dev” to start our development server. If you are currently logged in to your app, logout and signup again.

Put in an email address to sign up and hit the “Sign in with Email” button. You should receive an email from your “noreply” email address shortly. Clicking the button in the email should log you into the app locally.

If all this works without error, it’s time to publish to production

Testing Production

There are 2 steps here. First we’ll add our RESEND_API_KEY to our Environment Variables at Vercel and change our EMAIL_SERVER to our updated Resend SMTP setting. I copy and paste these from my .env.local file.

Next we’ll commit our config.js change in VS Code and sync it to GitHub. This will trigger a build at Vercel.

That’s all for production.

If you don’t currently send emails other than magic links, you can stop here.

Sending Emails

There is one more change to make if your application send emails other than magic links.

We’ll need to add a new file in the “libs” folder called “resend.js”. Copy and paste in the code below.

import { Resend } from 'resend';
import config from '@/config';

const resend = new Resend(process.env.RESEND_API_KEY);

export const sendEmail = async ({ to, subject, text, html, replyTo }) => {
  await resend.emails.send({
    from: config.mailgun.fromAdmin,
    to: to,
    reply_to: replyTo,
    subject: subject,
    text: text,
    html: html,
  });
};

Next, open a terminal in VS Code and run the following command

npm install resend

This will install the Resend node module.

Now anywhere you are importing “sendEmail” from the “@/libs/maligun.js” file, you can replace those imports with “@/libs/resend.js”. The call to “sendEmail” is the same.

Conclusion

I’ve done this change on 4 domains now and I can run through the changes in about 15 minutes. This effectively solves my 2 issues with Mailgun and I’m satisfied with the change.

I believe the setup is simpler with Resend on both their website and the DNS entries. The only drawback is that I now have a Resend account for every domain. It’s a trade off I can live with.

That’s it.

If you have questions or suggestions, please contact me by email, 𝕏/Twitter, Discord, etc.