Skip to content
Migrating from NextAuth.js v4? Read our migration guide.

Nodemailer Provider

개요

Nodemailer 프로바이더는 이메일을 통해 “매직 링크”를 보냅니다. 이 링크에는 검증 토큰이 포함된 URL이 있어, 사용자가 로그인할 수 있게 해줍니다.

하나 이상의 OAuth 서비스와 함께 이메일을 통한 로그인 기능을 추가하면, 사용자가 OAuth 계정에 접근할 수 없을 때(예: 계정이 잠기거나 삭제된 경우) 대체 로그인 방법을 제공할 수 있습니다.

Nodemailer 프로바이더는 하나 이상의 OAuth 프로바이더와 함께 사용하거나, 대신 사용할 수 있습니다.

작동 방식

처음 로그인할 때, 제공된 이메일 주소로 **인증 토큰(Verification Token)**이 전송됩니다. 기본적으로 이 토큰은 24시간 동안 유효합니다. 이 시간 내에 인증 토큰을 사용하면(예: 이메일의 링크를 클릭하면) 사용자 계정이 생성되고 로그인됩니다.

누군가가 로그인할 때 _기존 계정_의 이메일 주소를 제공하면, 이메일이 전송되고 이메일의 링크를 따라가면 해당 이메일 주소와 연결된 계정으로 로그인됩니다.

⚠️

Nodemailer 프로바이더는 JSON Web Token과 데이터베이스 관리 세션 모두와 함께 사용할 수 있지만, 데이터베이스를 구성해야 합니다. 데이터베이스를 사용하지 않고 이메일 로그인을 활성화하는 것은 불가능합니다.

설정

  1. Auth.js는 nodemailer를 의존성으로 포함하지 않습니다. Nodemailer 프로바이더를 사용하려면 직접 설치해야 합니다.
npm install nodemailer
  1. SMTP 계정이 필요합니다. 가능하면 nodemailer와 호환되는 서비스 중 하나를 사용하는 것이 좋습니다. Nodemailer는 다른 전송 방식과도 작동하지만, HTTP 기반 이메일 서비스를 사용하려면 ResendSendgrid와 같은 다른 Auth.js 프로바이더를 사용하는 것을 권장합니다.

  2. SMTP 서버 연결을 설정하는 방법은 두 가지입니다.

연결 문자열을 사용하거나 nodemailer 설정 객체를 사용할 수 있습니다.

.env
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com
./auth.ts
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: ...,
  providers: [
    Nodemailer({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
    }),
  ],
})
  1. 이메일 인증 토큰을 저장하기 위해 데이터베이스 어댑터 중 하나를 설정하는 것을 잊지 마세요.

  2. 이제 /api/auth/signin에서 이메일 주소로 로그인 프로세스를 시작할 수 있습니다.

사용자가 이메일 주소를 처음으로 인증할 때까지는 사용자 계정(즉, Users 테이블의 항목)이 생성되지 않습니다. 이미 계정과 연결된 이메일 주소인 경우, 사용자가 이메일의 링크를 사용하면 해당 계정으로 로그인됩니다.

Customization

이메일 본문

Nodemailer()sendVerificationRequest 옵션으로 커스텀 함수를 전달하여 전송되는 로그인 이메일을 완전히 커스터마이징할 수 있습니다.

./auth.ts
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Nodemailer({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      sendVerificationRequest({
        identifier: email,
        url,
        provider: { server, from },
      }) {
        // 여기에 함수를 작성
      },
    }),
  ],
})

예를 들어, 다음은 내장된 sendVerificationRequest() 메서드의 소스 코드입니다. 이 메서드에서 HTML을 렌더링(html())하고 이메일 제공자에게 네트워크 요청(transport.sendMail())을 보내 실제로 이메일을 전송하는 것을 확인할 수 있습니다.

import { createTransport } from "nodemailer"
 
export async function sendVerificationRequest(params) {
  const { identifier, url, provider, theme } = params
  const { host } = new URL(url)
  // 참고: `nodemailer`를 사용할 필요는 없으며, 원하는 것을 사용할 수 있습니다.
  const transport = createTransport(provider.server)
  const result = await transport.sendMail({
    to: identifier,
    from: provider.from,
    subject: `Sign in to ${host}`,
    text: text({ url, host }),
    html: html({ url, host, theme }),
  })
  const failed = result.rejected.concat(result.pending).filter(Boolean)
  if (failed.length) {
    throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
  }
}
 
function html(params: { url: string; host: string; theme: Theme }) {
  const { url, host, theme } = params
 
  const escapedHost = host.replace(/\./g, "​.")
 
  const brandColor = theme.brandColor || "#346df1"
  const color = {
    background: "#f9f9f9",
    text: "#444",
    mainBackground: "#fff",
    buttonBackground: brandColor,
    buttonBorder: brandColor,
    buttonText: theme.buttonText || "#fff",
  }
 
  return `
<body style="background: ${color.background};">
  <table width="100%" border="0" cellspacing="20" cellpadding="0"
    style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
    <tr>
      <td align="center"
        style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
        Sign in to <strong>${escapedHost}</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="${color.buttonBackground}"><a href="${url}"
                target="_blank"
                style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; 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: ${color.text};">
        If you did not request this email you can safely ignore it.
      </td>
    </tr>
  </table>
</body>
`
}
 
// 이메일 텍스트 본문 (HTML을 렌더링하지 않는 이메일 클라이언트를 위한 대체 텍스트)
function text({ url, host }: { url: string; host: string }) {
  return `Sign in to ${host}\n${url}\n\n`
}

React를 사용하여 다양한 이메일 클라이언트와 호환되는 멋진 이메일을 생성하려면 mjml 또는 react-email을 확인해 보세요.

검증 토큰

기본적으로 우리는 무작위 검증 토큰을 생성합니다. 만약 이를 재정의하고 싶다면, 프로바이더 옵션에 generateVerificationToken 메서드를 정의할 수 있습니다:

./auth.ts
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Nodemailer({
      async generateVerificationToken() {
        return crypto.randomUUID()
      },
    }),
  ],
})

이메일 주소 정규화

기본적으로 Auth.js는 이메일 주소를 정규화합니다. 이메일 주소를 대소문자를 구분하지 않게 처리하며(이는 기술적으로 RFC 2821 스펙을 준수하지 않지만, 실제로는 데이터베이스에서 이메일로 사용자를 조회할 때 더 많은 문제를 일으킬 수 있습니다.), 또한 쉼표로 구분된 목록으로 전달된 보조 이메일 주소를 제거합니다. Nodemailer 프로바이더의 normalizeIdentifier 메서드를 통해 여러분만의 정규화를 적용할 수 있습니다. 다음 예제는 기본 동작을 보여줍니다:

./auth.ts
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Nodemailer({
      normalizeIdentifier(identifier: string): string {
        // 사용자 입력에서 `@`로 구분된 처음 두 요소만 가져옵니다.
        let [local, domain] = identifier.toLowerCase().trim().split("@")
        // "@" 앞 부분은 ","를 포함할 수 있지만,
        // 도메인 부분에서는 이를 제거합니다.
        domain = domain.split(",")[0]
        return `${local}@${domain}`
 
        // 오류를 던질 수도 있으며, 이 경우 사용자는
        // URL에 error=EmailSignin이 포함된 로그인 페이지로 리디렉션됩니다.
        // if (identifier.split("@").length > 2) {
        //   throw new Error("Only one email allowed")
        // }
      },
    }),
  ],
})
⚠️

여러 개의 이메일 주소가 전달되더라도 항상 단일 이메일 주소를 반환하도록 해야 합니다.

Auth.js © Balázs Orbán and Team - 2025