βοΈ Mailgun Setup¶
π Introduction¶
Mailgun is not required, but youβll need an email service to handle magic login links, abandoned cart emails, and more.
β οΈ Notes:¶
- Mailgun silently removed the "pay-as-you-go" tier from the pricing page, but it still exists.
- π‘ Tip: Start with the $35 trial and cancel it.
- π Result: Youβll be downgraded to the free βpay-as-you-goβ tier automatically.
- π° Cost: If you send fewer than 1,000 emails per month, itβs only $1/month.
βοΈ Setup¶
1οΈβ£ Create a new Mailgun account.
2οΈβ£ Under [Sending], go to [Domains] β [+ Add Domain]
- π Recommended: Use a subdomain like mail.yourdomain.com
.
3οΈβ£ If using the EU region:
- Add the following in libs/mailgun.js
:
url: "https://api.eu.mailgun.net/"
````
4οΈβ£ Complete all DNS verification steps
* π If using a subdomain, make sure your DNS records are updated.
5οΈβ£ Add an additional DMARC record to improve deliverability:
6οΈβ£ SMTP Setup:
* Go to \[Domain Settings] β \[SMTP Credentials] β \[Reset Password]
* Choose \[Automatic] β \[Create Password]
* Click \[Copy] in the bottom-right of the popup
* π In `.env.local`, set:
```bash
smtp://postmaster@[mail.yourdomain.com]:[copied_password]@smtp.mailgun.org:587
7οΈβ£ API Key Setup:
- Under [Sending API Keys], click [Create sending key]
- Add to
.env.local
asMAILGUN_API_KEY
8οΈβ£ Update config.js
:
mailgun: {
subdomain: "mg",
fromNoReply: `SaaSFast <noreply@mg.SaaSfa.st>`,
fromAdmin: `Marc at SaaSFast <marc@mg.SaaSfa.st>`,
supportEmail: "marc@mg.SaaSfa.st",
forwardRepliesTo: "marc.louvion@gmail.com",
}
9οΈβ£ TypeScript Config (optional):
In types/config.ts
, add:
mailgun: {
subdomain: string;
fromNoReply: string;
fromAdmin: string;
supportEmail?: string;
forwardRepliesTo?: string;
};
ποΈ Mailgun Library Setup¶
π Create libs/mailgun.js
with:
import config from "@/config";
const formData = require("form-data");
const Mailgun = require("mailgun.js");
const mailgun = new Mailgun(formData);
const mg = mailgun.client({
username: "api",
key: process.env.MAILGUN_API_KEY || "dummy",
});
export const sendEmail = async ({ to, subject, text, html, replyTo }) => {
const data = {
from: config.mailgun.fromAdmin,
to: [to],
subject,
text,
html,
...(replyTo && { "h:Reply-To": replyTo }),
};
await mg.messages.create(
(config.mailgun.subdomain ? `${config.mailgun.subdomain}.` : "") + config.domainName,
data
);
};
11οΈβ£ Update EmailProvider
in libs/next-auth.js
:
EmailProvider({
server: process.env.EMAIL_SERVER,
from: config.mailgun.fromNoReply,
}),
12οΈβ£ Replace all config.resend
with config.mailgun
, and libs/resend
with libs/mailgun
across your codebase.
π§ Receiving Emails¶
13οΈβ£ Go to [Receiving] β [Create Route]:
- Choose [Match Recipient]
- Add the email address you'll send from (e.g.
name@mail.yourdomain.com
) - Forward to:
https://[your-domain].com/api/webhook/mailgun
- Click [Create Route]
14οΈβ£ Webhook Setup:
- Go to [Sending] β [Webhooks]
- Copy the HTTP Webhook Signing Key
- Add to
.env.local
asMAILGUN_SIGNING_KEY
15οΈβ£ Add a forwarding email (e.g., your Gmail) to mailgun.forwardRepliesTo
in config.js
.
ποΈ Create Mailgun Webhook Route¶
16οΈβ£ Create app/api/webhook/mailgun/route.js
:
import { NextResponse } from "next/server";
import { sendEmail } from "@/libs/mailgun";
import config from "@/config";
import crypto from "crypto";
export async function POST(req) {
try {
const formData = await req.formData();
const signingKey = process.env.MAILGUN_SIGNING_KEY;
const timestamp = formData.get("timestamp").toString();
const token = formData.get("token").toString();
const signature = formData.get("signature").toString();
const hash = crypto.createHmac("sha256", signingKey)
.update(timestamp + token)
.digest("hex");
if (hash !== signature) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
const sender = formData.get("From");
const subject = formData.get("Subject");
const html = formData.get("body-html");
if (config.mailgun.forwardRepliesTo && html && subject && sender) {
await sendEmail({
to: config.mailgun.forwardRepliesTo,
subject: `${config?.appName} | ${subject}`,
html: `<div><p><b>- Subject:</b> ${subject}</p><p><b>- From:</b> ${sender}</p><p><b>- Content:</b></p><div>${html}</div></div>`,
replyTo: sender,
});
}
return NextResponse.json({});
} catch (e) {
console.error(e?.message);
return NextResponse.json({ error: e?.message }, { status: 500 });
}
}
π DNS Records Check¶
Check DNS record validity using MxToolbox (enter the subdomain if applicable).
βοΈ Sending Emails¶
-
SMTP
-
Used for sending magic login links.
-
Mailgun API
-
Use
sendEmail()
inlibs/mailgun.js
for everything else.
π₯ Receiving Emails¶
- π« Mailgun does not store or forward emails by default.
- Thatβs why we created a route to handle emails sent to
mailgun.supportEmail
fromconfig.js
and redirect them to your API. - π§ Replies will automatically include the original sender in the reply-to field, so you can reply directly from your inbox.
β Spam Prevention Checklist¶
π· Thereβs an image here