Start your developer blog today!
Email me

How to Customize Magic Link Emails from Passwordless Authentication in NextAuth

Apr 22, 2021 3 min read

Sending magic links with passwordless authentication with NextAuth is quite simple. With emails relayed through an SMTP server, the next step is to customize those emails.

NextAuth has a default email template that we can modify and style to our liking.

The first step is to override the sendVerificationRequest function with our own custom request.

// pages/api/auth/[...nextauth].js
export default NextAuth({
  providers: [
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      sendVerificationRequest: customVerificationRequest,

We’ll be using the default sendVerificationRequest function, but replacing the html() function, which holds the email’s HTML body, with our own.

Whether we define this custom function inside pages/api/auth/[...nextauth].js or in another utility file is up to you.

import nodemailer from "nodemailer";
const customVerificationRequest = ({
  identifier: email, url, token, baseUrl, provider
}) => {
  return new Promise((resolve, reject) => {
    const { server, from } = provider;
    const site = baseUrl.replace(/^https?:\/\//, "");
      to: email,
      subject: `Sign in to ${site}`,
      text: text({ url, site, email }),
      html: html({ url, site, email }),
    }, (error) => {
      if (error) {
        logger.error("SEND_VERIFICATION_EMAIL_ERROR", email, error);
        return reject(new Error("SEND_VERIFICATION_EMAIL_ERROR", error));
      return resolve();

Then, we can define our custom html() function here. There are some default styles defined in the beginning. As we can see, the function returns a stringified DOM tree.

const html = ({ url, site, email }) => {
  const escapedEmail = `${email.replace(/\./g, "​.")}`;
  const escapedSite = `${site.replace(/\./g, "​.")}`;
  const backgroundColor = "#f9f9f9";
  const textColor = "#444444";
  const mainBackgroundColor = "#ffffff";
  const buttonBackgroundColor = "#346df1";
  const buttonBorderColor = "#346df1";
  const buttonTextColor = "#ffffff";
  return `
    <body style="background: ${backgroundColor};">
      <table width="100%" border="0" cellspacing="0" cellpadding="0">
          <td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
      <table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
          <td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
            Sign in as <strong>${escapedEmail}</strong>
          <td align="center" style="padding: 20px 0;">
            <table border="0" cellspacing="0" cellpadding="0">
                <td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; text-decoration: none;border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
          <td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
            If you did not request this email you can safely ignore it.

Finally, we can also modify the text() function, which serves as a fallback for email clients that don’t render HTML.

const text = ({ url, site }) => `Sign in to ${site}\n${url}\n\n`;

More JavaScript Articles