Files
chocoadmin/lib/mail.ts
T

159 lines
4.9 KiB
TypeScript

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<boolean> {
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<boolean> {
const subject = `[초코마에] ${maestroName} - 마에스트로 계정 유효기간 연장 완료`;
const html = `안녕하세요.<br/>
[초코마에] 마에스트로 계정의 유효기간 연장을 신청해 주셔서 감사합니다.<br/>
<br/>
${maestroName} - 마에스트로 계정의 유효기간이 +1년 연장되었습니다.<br/>
* 새로운 유효기간 : ${newAvailableDate}<br/>
<br/>
* 상위 요금제로 업그레이드 하시면, 유효기간이 업그레이드한 날짜로부터 1년으로 갱신됩니다.<br/>
* 유효기간이 끝나면, 비활성화된 계정과 그 안에 있는 모든 정보가 6개월 후에 자동 삭제됩니다.<br/>
<br/>
감사합니다.`;
return sendMail({ to: maestroEmail, subject, html });
}
export async function sendUpgradeDoneEmail(
maestroName: string,
maestroEmail: string,
registeredActivateStatus: number,
registeredAccountType: number,
requestedAccountType: number,
newAvailableDate: string
): Promise<boolean> {
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 = `안녕하세요.<br/>
[초코마에] 마에스트로 계정 요금제 업그레이드를 신청해주셔서 감사합니다.<br/>
<br/>
${maestroName} - 마에스트로 계정의 요금제가 [ ${upgradeFromText} ] 에서 [ ${upgradeStudentCount}명 (${upgradePrice}원) ] 으로 업그레이드 되었습니다.<br/>
<br/>
* 마에스트로 계정 유효기간이 오늘부터 1년으로 설정되었습니다.<br/>
<br/>
<br/>
등록하신 마에스트로 계정 정보는 마에스트로 로그인 후, [계정 정보 수정] 화면에서 변경이 가능합니다.<br/>
(변경 가능 정보 : 마에스트로 아이디, 이메일, 암호)<br/>
<br/>
감사합니다.`;
return sendMail({ to: maestroEmail, subject, html });
}