Skip to content
shipanysaas

How to add Resend email to a Next.js + Supabase app

Send email from Next.js with the Resend API and route Supabase Auth emails through Resend SMTP: API key, verified domain, React Email and limits.

A Next.js app on Supabase sends two kinds of email. Supabase Auth sends sign-up confirmations, magic links and password resets. Your own code sends everything else: team invites, receipts, alerts. Resend can carry both. Your server calls the Resend API for your app's own email, and Supabase Auth sends its email through Resend's SMTP server.

How do you add it? Create a Resend API key and verify a sending domain. Install the resend package and call resend.emails.send() from server-only code, such as a Server Action or route handler, with a React Email template. Then turn on custom SMTP in Supabase with Resend's SMTP details, so auth emails reach real users. Here is each step, and what breaks if you skip one.

Two kinds of email, two connections

EmailWho sends itHow it reaches Resend
Confirm sign-up, magic link, password reset, email changeSupabase AuthCustom SMTP: smtp.resend.com
Invites, receipts, notifications, contact formYour server codeThe Resend API, through the SDK or plain HTTP

You can set up one without the other. Most apps need both before launch.

Step 1: create a Resend API key and verify a domain

In the Resend dashboard, create an API key and store it as RESEND_API_KEY on the server. Resend shows the value once. A key can have full_access, or sending_access that can only send email, and a sending key can be limited to one domain. Use a sending key in your app.

Then add a domain. Resend's domain guide asks you to send from a domain you own, and strongly recommends a subdomain, such as updates.example.com rather than example.com, to keep the reputation of your mail separate. Resend gives you DKIM and SPF records to add at your DNS host, as TXT and MX or CNAME records. A few things save time:

  • Copy the records exactly. One wrong character keeps the domain unverified.
  • If a CNAME record is proxied at your DNS host, such as Cloudflare's orange cloud, turn the proxy off for it.
  • Verification often completes within 15 minutes, but DNS can take up to 72 hours.
  • After verification, add a DMARC record. It tells inboxes what to do with mail that fails the checks.

Step 2: send email with the Resend API from server code

Install the SDK with npm install resend and keep the client in a server-only module. The key must never reach the browser, so nothing here runs in a Client Component:

// lib/email.ts
import 'server-only';

import { Resend } from 'resend';

import { WelcomeEmail } from '@/emails/welcome';

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

export async function sendWelcomeEmail(to: string, name: string) {
  const { error } = await resend.emails.send({
    from: 'Acme <hello@updates.example.com>',
    to: [to],
    subject: 'Welcome to Acme',
    react: WelcomeEmail({ name }),
  });

  if (error) {
    throw new Error(`Email not sent: ${error.message}`);
  }
}

Call it from a Server Action or a route handler, after the work that triggers it has succeeded. Three details from Resend's Next.js guide are easy to miss:

  • Check error. emails.send() returns { data, error } instead of throwing, so a failed send is silent unless you look.
  • Pass the template as a function call. Write WelcomeEmail({ name }), not <WelcomeEmail name={name} />.
  • Use your own domain in from. Resend's examples use onboarding@resend.dev, which is for testing only.

If a request can be retried, add an idempotency key, so a retry doesn't send the email twice.

Step 3: write the template with React Email

React Email lets you write emails as React components that render to HTML email clients can handle, instead of hand-built tables:

// emails/welcome.tsx
import { Body, Button, Html, Preview, Text } from 'react-email';

export function WelcomeEmail({ name }: { name: string }) {
  return (
    <Html>
      <Preview>Your account is ready</Preview>
      <Body>
        <Text>Hi {name}, your Acme account is ready.</Text>
        <Button href="https://example.com/dashboard">Open Acme</Button>
      </Body>
    </Html>
  );
}

Templates live in your repository, so they are reviewed and versioned like the rest of your code. If you would rather send HTML, await React Email's render() on the component and pass the string it returns as html.

Escape anything a user typed before it goes into an email. React escapes text in JSX for you, but a value you insert as raw HTML can carry markup or links into someone's inbox.

Step 4: send Supabase Auth emails through Resend SMTP

Supabase's built-in sender is for trying things out. Supabase's custom SMTP guide says it only delivers to members of your project's team, and its limit is currently 2 messages an hour. Real users get "Email address not authorized".

Resend's Supabase guide gives the settings. In the Supabase dashboard, open Authentication, then Email under Notifications, then SMTP Settings:

SettingValue
Hostsmtp.resend.com
Port465
Usernameresend
PasswordYour Resend API key
Sender email and nameAn address on your verified domain, such as no-reply@auth.example.com

Once saved, Supabase sends every auth email through Resend. It starts you at 30 messages an hour to protect your new sender, and you can raise that on the rate limits page. A hosted project doesn't read your local supabase/config.toml, so set the email templates in the dashboard as well.

Supabase suggests keeping auth mail and marketing mail on separate domains, such as auth.example.com and marketing.example.com. If one domain's reputation drops, sign-in emails keep arriving.

When emails don't arrive

What you seeUsual cause
Resend refuses the sendThe from domain isn't verified yet, or it doesn't match a sending key's domain
"Email address not authorized" from SupabaseCustom SMTP is off, so only your team's addresses get mail
"Email rate limit exceeded" from SupabaseThe hourly limit is reached; raise it after setting up SMTP
Mail lands in spamDKIM or SPF is missing, or there is no DMARC record
A 429 from ResendToo many requests at once; Resend's default limit is 10 per second per team
The same email arrives twiceA retried request without an idempotency key

How the kit does it

shipanysaas sends both kinds of email out of the box. The app's own email goes through @repo/mailers in packages/mailers, and MAILER_PROVIDER picks the transport:

  • nodemailer, the default, sends through any SMTP server and runs on the Node.js runtime only.
  • resend sends through Resend, with RESEND_API_KEY.
  • log sends nothing and logs only the recipient's domain and the subject. The live demo uses it.

The Resend mailer in packages/mailers/resend calls Resend's HTTP API with fetch, so it needs no SDK. Every message goes out from EMAIL_SENDER. The templates are React Email components in packages/email-templates/src/emails/, rendered to HTML with render(), with their text in locale files. They include team invitations, one-time codes for deleting an account or team or transferring ownership, and the account-deletion confirmation. The contact form and showcase submissions are emailed to CONTACT_EMAIL. Values a user typed are escaped before they go into an email's HTML.

Supabase Auth's templates are in apps/web/supabase/templates/; in production you set them, and the SMTP settings above, in your Supabase project. Locally, both kinds of email land in Mailpit at http://localhost:54324, and the developer tool at http://localhost:3010 previews the app's templates. To add another provider, add its name to provider-enum.ts and register it in registry.ts, both in packages/mailers/core/src.

The live demo has emails turned off, so invitations and codes there are logged, not sent. The full list is in Email, and the plans are on the pricing page. If magic links are next on your list, the magic link guide uses this SMTP setup.

Questions people ask

Do I need the Resend SDK?

No. The Resend API is a REST API: a POST to https://api.resend.com/emails with your key as a bearer token works from any runtime that has fetch. The SDK adds types and the React shortcut.

Can Supabase Auth use the Resend API instead of SMTP?

Supabase also supports a Send Email hook, which lets your own code send auth emails, and that code can call the Resend API. Custom SMTP is less work and covers most apps.

Which port should I use for Resend SMTP?

Resend's Supabase guide uses port 465.

Can I send from my root domain?

You can, but Resend recommends a subdomain, so a problem with one kind of mail doesn't hurt the reputation of the rest.

Live demo

Resets hourly

Get the kit