Files
tier-maker/backend/src/lib/mailer.js

114 lines
4.5 KiB
JavaScript

const nodemailer = require('nodemailer')
const SMTP_HOST = process.env.SMTP_HOST || 'smtp.gmail.com'
const SMTP_PORT = process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : 465
const SMTP_SECURE = process.env.SMTP_SECURE ? process.env.SMTP_SECURE === 'true' : SMTP_PORT === 465
const SMTP_USER = process.env.SMTP_USER || ''
const SMTP_PASS = process.env.SMTP_PASS || ''
const SMTP_FROM = process.env.SMTP_FROM || SMTP_USER
let transporterPromise = null
function isMailerConfigured() {
return !!SMTP_USER && !!SMTP_PASS && !!SMTP_FROM
}
async function getTransporter() {
if (!isMailerConfigured()) {
const error = new Error('mail_not_configured')
error.code = 'mail_not_configured'
throw error
}
if (!transporterPromise) {
transporterPromise = (async () => {
const transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: SMTP_PORT,
secure: SMTP_SECURE,
auth: {
user: SMTP_USER,
pass: SMTP_PASS,
},
})
await transporter.verify()
return transporter
})()
}
return transporterPromise
}
async function sendMail({ to, subject, text, html }) {
const transporter = await getTransporter()
await transporter.sendMail({
from: SMTP_FROM,
to,
subject,
text,
html,
})
}
async function sendEmailVerificationMail({ to, nickname, verificationUrl }) {
const displayName = nickname || to.split('@')[0] || '사용자'
await sendMail({
to,
subject: '[Tier Maker] 이메일 인증을 완료해주세요',
text: [
`${displayName}님, Tier Maker 가입을 완료하려면 아래 링크로 이메일 인증을 진행해주세요.`,
'',
verificationUrl,
'',
'이 링크는 24시간 동안 유효합니다.',
'직접 요청하지 않았다면 이 메일은 무시하셔도 됩니다.',
].join('\n'),
html: `
<div style="font-family:Arial,sans-serif;line-height:1.7;color:#111827">
<h1 style="font-size:20px;margin:0 0 16px">Tier Maker 이메일 인증</h1>
<p style="margin:0 0 16px">${displayName}님, Tier Maker 가입을 완료하려면 아래 버튼으로 이메일 인증을 진행해주세요.</p>
<p style="margin:0 0 20px">
<a href="${verificationUrl}" style="display:inline-block;padding:12px 18px;border-radius:999px;background:#4c85f5;color:#ffffff;text-decoration:none;font-weight:700">이메일 인증하기</a>
</p>
<p style="margin:0 0 8px;font-size:13px;color:#6b7280">버튼이 열리지 않으면 아래 주소를 브라우저에 직접 붙여넣어주세요.</p>
<p style="margin:0 0 20px;font-size:13px;word-break:break-all;color:#2563eb">${verificationUrl}</p>
<p style="margin:0;font-size:13px;color:#6b7280">이 링크는 24시간 동안 유효합니다. 직접 요청하지 않았다면 이 메일은 무시하셔도 됩니다.</p>
</div>
`,
})
}
async function sendPasswordResetMail({ to, nickname, resetUrl }) {
const displayName = nickname || to.split('@')[0] || '사용자'
await sendMail({
to,
subject: '[Tier Maker] 비밀번호 재설정 안내',
text: [
`${displayName}님, Tier Maker 비밀번호를 다시 설정하려면 아래 링크를 열어주세요.`,
'',
resetUrl,
'',
'이 링크는 1시간 동안 유효합니다.',
'직접 요청하지 않았다면 이 메일은 무시하셔도 됩니다.',
].join('\n'),
html: `
<div style="font-family:Arial,sans-serif;line-height:1.7;color:#111827">
<h1 style="font-size:20px;margin:0 0 16px">Tier Maker 비밀번호 재설정</h1>
<p style="margin:0 0 16px">${displayName}님, 비밀번호를 다시 설정하려면 아래 버튼을 눌러주세요.</p>
<p style="margin:0 0 20px">
<a href="${resetUrl}" style="display:inline-block;padding:12px 18px;border-radius:999px;background:#4c85f5;color:#ffffff;text-decoration:none;font-weight:700">비밀번호 재설정</a>
</p>
<p style="margin:0 0 8px;font-size:13px;color:#6b7280">버튼이 열리지 않으면 아래 주소를 브라우저에 직접 붙여넣어주세요.</p>
<p style="margin:0 0 20px;font-size:13px;word-break:break-all;color:#2563eb">${resetUrl}</p>
<p style="margin:0;font-size:13px;color:#6b7280">이 링크는 1시간 동안 유효합니다. 직접 요청하지 않았다면 이 메일은 무시하셔도 됩니다.</p>
</div>
`,
})
}
module.exports = {
isMailerConfigured,
sendEmailVerificationMail,
sendPasswordResetMail,
}