Send email from Supabase, Next.js or Node
Password resets, magic links and receipts need a sender that is authenticated for your domain. Here is the same job four ways: Supabase Auth over SMTP, Nodemailer, plain fetch from Next.js, and the Resend SDK.
Updated 3 min read
On this page (6)
Before you start
You need two things from the Mailbay dashboard:
- A verified domain. App email goes out from your own domain, such as
noreply@acme.com. Add it with the new project wizard; if your domain already has mail, choose sending only or themail.subdomain option and nothing about your existing mail changes. See support@ on your own domain for the walk through. - A sending key. On the API keys page, create a key of type Sending only, optionally limited to one domain. It can send and read the send log, and nothing else, so it is the right key to paste into a third party dashboard.
Every example below uses the same SMTP settings or the same API call:
| Host | smtp.mailbay.dev |
|---|---|
| Port | 465 (TLS from the first byte) or 587 (STARTTLS) |
| Username | mailbay |
| Password | a Mailbay sending key, mb_live_… |
| From address | any address on your verified domain, such as noreply@acme.com |
Supabase Auth
Supabase sends sign up confirmations, magic links and password resets for you, and lets you point that mail at your own SMTP server. In your project's Auth settings, enable custom SMTP and fill in:
Sender email noreply@acme.com
Sender name Acme
Host smtp.mailbay.dev
Port number 465
Username mailbay
Password mb_live_xxxxxxxxx (your sending key)Then edit the email templates in Supabase as usual; Mailbay only carries the mail. Supabase's own guide is here. The same settings work for any tool that asks for SMTP, such as WordPress or a self hosted app.
Nodemailer
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
host: "smtp.mailbay.dev",
port: 465,
secure: true, // or port 587 with secure: false, which upgrades with STARTTLS
auth: { user: "mailbay", pass: process.env.MAILBAY_API_KEY },
});
const info = await transporter.sendMail({
from: '"Acme" <noreply@acme.com>',
to: "delivered@test.mailbay.dev",
subject: "Welcome to Acme",
text: "Thanks for signing up.",
});
console.log(info.response); // 250 2.0.0 OK queued as eml_01m4x49s44h10nmssw7av01h9vMailbay answers 250 only once the email is stored and queued, and the id in the reply is the one you see in the Sending log. A from address outside your verified domains is refused during the SMTP session with 550 5.7.1 and a sentence saying which domain to verify.
Next.js route handler
No SDK needed: POST /v1/emails takes a JSON body. Keep the key on the server, in a route handler or server action.
export async function POST(request: Request) {
const { email, resetUrl } = await request.json();
const res = await fetch("https://api.mailbay.dev/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAILBAY_API_KEY}`,
"Content-Type": "application/json",
// A retry with the same key within 24 hours never sends twice.
"Idempotency-Key": `reset-${email}-${resetUrl}`,
},
body: JSON.stringify({
from: "Acme <noreply@acme.com>",
to: email,
subject: "Reset your password",
text: `Reset your password: ${resetUrl}`,
html: `<p><a href="${resetUrl}">Reset your password</a></p>`,
}),
});
const data = await res.json();
if (!res.ok) return Response.json(data, { status: res.status }); // { error: { code, message, fix } }
return Response.json({ id: data.id });
}The body also takes cc, bcc, reply_to, headers, tags and base64 attachments. Errors come back as {"error": {"code", "message", "fix"}}, where fix says what to change.
The Resend SDK
Mailbay's /v1/emails accepts Resend's request body, and the official resend Node package reads its base URL from the environment. If your code already uses it, switching is two variables:
RESEND_BASE_URL=https://api.mailbay.dev/v1
RESEND_API_KEY=mb_live_xxxxxxxxximport { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
const { data, error } = await resend.emails.send({
from: "Acme <noreply@acme.com>",
to: ["delivered@test.mailbay.dev"],
subject: "Hello from the Resend SDK",
html: "<strong>It works</strong>",
});resend 6.32.1: emails.send and emails.get work. Resend features Mailbay does not have, such as react, templates, scheduling and batch sends, return 400 unsupported_field. The full list is in Mailbay vs Resend.Check what happened
Every email, whether it came over SMTP or the API, is in the project's Sending log with a status per recipient: queued, sent, delivered, deferred, bounced, failed or complained. From code:
curl https://api.mailbay.dev/v1/emails/eml_01m4x49ktbe6xvxfxyq5hwc6nx \
-H "Authorization: Bearer $MAILBAY_API_KEY"
# {"object":"email", "status":"delivered", "last_event":"delivered",
# "recipients":[{"address":"delivered@test.mailbay.dev","status":"delivered",
# "response":"250 2.0.0 Simulated delivery (Mailbay test recipient)"}], ...}- Send to
delivered@,bounced@orcomplained@test.mailbay.devto see each outcome without real delivery. - Hard bounces and complaints add the address to your suppressions, and later sends to it fail fast with
recipient_suppressed. - For events in your app, add a webhook for
message.deliveredandmessage.bounced: see Webhooks. - New accounts send to at most 100 recipients a day for their first 7 days. It protects the shared sending IPs while we, and you, build a reputation.
Read next
- Mailbay vs ResendResend is a polished, proven sending API. Mailbay accepts the same request body for sending, so moving is a base URL and a key, and it adds inboxes people and agents read.
- How email authentication works: SPF, DKIM and DMARC explained with real recordsThree DNS records decide whether your mail is trusted. Each one answers a different question. Here they are, one at a time, using the exact records Mailbay generates for a new domain.
- support@ on your own domain, without Google WorkspaceMost small SaaS products need two or three shared addresses, not a seat per person. Here is how to run support@ on your domain, keep the mail you already have, and answer from a shared inbox or Apple Mail.
Join the beta
Mailbay is in private beta and free while the beta runs: inboxes, sending, your own domains and MCP. We invite people in small batches and read every note on the form.