114 lines
4.5 KiB
JavaScript
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,
|
|
}
|