✒️
Start your developer blog today!
✉️
Questions?
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: [
    Providers.Email({
      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?:\/\//, "");
    nodemailer.createTransport(server).sendMail({
      to: email,
      from,
      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">
        <tr>
          <td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
            <strong>${escapedSite}</strong>
          </td>
        </tr>
      </table>
      <table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
        <tr>
          <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>
        </tr>
        <tr>
          <td align="center" style="padding: 20px 0;">
            <table border="0" cellspacing="0" cellpadding="0">
              <tr>
                <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>
              </tr>
            </table>
          </td>
        </tr>
        <tr>
          <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.
          </td>
        </tr>
      </table>
    </body>`;
};

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