import nodemailer from "nodemailer"; import { ACTIVATE_STATUSES } from "@/lib/constants"; import { logger } from "@/lib/logger"; import { getMailConfig } from "@/lib/mail-config"; import { getAccountTypePlayerCount, getAccountTypePrice, } from "@/lib/utils"; const CTX = "mail"; function createTransporter() { const cfg = getMailConfig(); return nodemailer.createTransport({ host: cfg.smtpHost, port: cfg.smtpPort, secure: cfg.smtpSecure, auth: { user: cfg.smtpUser, pass: cfg.smtpPassword, }, }); } type SendMailOptions = { to: string; subject: string; html: string; }; const isDetailedLog = () => process.env.APP_ENV !== "production"; async function sendMail(options: SendMailOptions): Promise { const cfg = getMailConfig(); const detailed = isDetailedLog(); if (!cfg.sendEnabled) { logger.info(CTX, "send skipped (MAIL_SEND_ENABLED=false)", { to: cfg.overrideTo ?? options.to, subject: options.subject, }); return false; } const to = cfg.overrideTo ?? options.to; const bcc = cfg.bccAddress || undefined; if (detailed) { logger.debug(CTX, "sending", { to, ...(bcc && { bcc }), subject: options.subject, smtp: `${cfg.smtpUser}@${cfg.smtpHost}:${cfg.smtpPort}`, ...(cfg.overrideTo && { overrideTo: cfg.overrideTo }), }); } const t0 = Date.now(); try { const transporter = createTransporter(); const info = await transporter.sendMail({ from: `"${cfg.fromName}" <${cfg.fromAddress}>`, replyTo: cfg.fromAddress, to, bcc, subject: options.subject, html: options.html, }); const duration = Date.now() - t0; if (detailed) { logger.info(CTX, "send ok", { to, subject: options.subject, messageId: info.messageId, duration, }); } else { logger.info(CTX, "send ok", { duration }); } return true; } catch (err) { const duration = Date.now() - t0; if (detailed) { logger.error(CTX, "send failed", { to, subject: options.subject, duration, error: err instanceof Error ? err.message : String(err), ...(err instanceof Error && err.stack && { stack: err.stack }), }); } else { logger.error(CTX, "send failed", { duration }); } return false; } } export async function sendExtensionDoneEmail( maestroName: string, maestroEmail: string, newAvailableDate: string ): Promise { const subject = `[초코마에] ${maestroName} - 마에스트로 계정 유효기간 연장 완료`; const html = `안녕하세요.
[초코마에] 마에스트로 계정의 유효기간 연장을 신청해 주셔서 감사합니다.

${maestroName} - 마에스트로 계정의 유효기간이 +1년 연장되었습니다.
* 새로운 유효기간 : ${newAvailableDate}

* 상위 요금제로 업그레이드 하시면, 유효기간이 업그레이드한 날짜로부터 1년으로 갱신됩니다.
* 유효기간이 끝나면, 비활성화된 계정과 그 안에 있는 모든 정보가 6개월 후에 자동 삭제됩니다.

감사합니다.`; return sendMail({ to: maestroEmail, subject, html }); } export async function sendUpgradeDoneEmail( maestroName: string, maestroEmail: string, registeredActivateStatus: number, registeredAccountType: number, requestedAccountType: number, newAvailableDate: string ): Promise { const subject = `[초코마에] ${maestroName} - 마에스트로 계정의 요금제 업그레이드 완료 알림`; const prevStudentCount = getAccountTypePlayerCount(registeredAccountType); const upgradeStudentCount = getAccountTypePlayerCount(requestedAccountType); const upgradePrice = getAccountTypePrice(requestedAccountType).toLocaleString("ko-KR"); let upgradeFromText: string; if (registeredActivateStatus === ACTIVATE_STATUSES.TRIAL) { upgradeFromText = `${prevStudentCount}명 (0원 : 무료 체험 기간)`; } else { const prevPrice = getAccountTypePrice(registeredAccountType).toLocaleString("ko-KR"); upgradeFromText = `${prevStudentCount}명 (${prevPrice}원)`; } const html = `안녕하세요.
[초코마에] 마에스트로 계정 요금제 업그레이드를 신청해주셔서 감사합니다.

${maestroName} - 마에스트로 계정의 요금제가 [ ${upgradeFromText} ] 에서 [ ${upgradeStudentCount}명 (${upgradePrice}원) ] 으로 업그레이드 되었습니다.

* 마에스트로 계정 유효기간이 오늘부터 1년으로 설정되었습니다.


등록하신 마에스트로 계정 정보는 마에스트로 로그인 후, [계정 정보 수정] 화면에서 변경이 가능합니다.
(변경 가능 정보 : 마에스트로 아이디, 이메일, 암호)

감사합니다.`; return sendMail({ to: maestroEmail, subject, html }); }