summaryrefslogtreecommitdiff
path: root/lib/mail
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
commit1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch)
tree8a5587f10ca55b162d7e3254cb088b323a34c41b /lib/mail
initial commit
Diffstat (limited to 'lib/mail')
-rw-r--r--lib/mail/mailer.ts31
-rw-r--r--lib/mail/sendEmail.ts36
-rw-r--r--lib/mail/templates/admin-created.hbs78
-rw-r--r--lib/mail/templates/admin-email-changed.hbs90
-rw-r--r--lib/mail/templates/otp.hbs77
-rw-r--r--lib/mail/templates/rfq-invite.hbs116
-rw-r--r--lib/mail/templates/vendor-active.hbs51
-rw-r--r--lib/mail/templates/vendor-pq-comment.hbs128
-rw-r--r--lib/mail/templates/vendor-pq-status.hbs48
9 files changed, 655 insertions, 0 deletions
diff --git a/lib/mail/mailer.ts b/lib/mail/mailer.ts
new file mode 100644
index 00000000..e0a90f1e
--- /dev/null
+++ b/lib/mail/mailer.ts
@@ -0,0 +1,31 @@
+import nodemailer from 'nodemailer';
+import handlebars from 'handlebars';
+import fs from 'fs';
+import path from 'path';
+import i18next from 'i18next';
+
+// Nodemailer Transporter 생성
+const transporter = nodemailer.createTransport({
+ host: process.env.Email_Host,
+ port: 465,
+ secure: true,
+ auth: {
+ user: process.env.Email_User_Name,
+ pass: process.env.Email_Password,
+ },
+});
+
+// Handlebars 템플릿 로더 함수
+function loadTemplate(templateName: string, data: Record<string, any>) {
+ const templatePath = path.join(process.cwd(), 'lib', 'mail', 'templates', `${templateName}.hbs`);
+ const source = fs.readFileSync(templatePath, 'utf8');
+ const template = handlebars.compile(source);
+ return template(data);
+}
+
+handlebars.registerHelper('t', function(key: string, options: any) {
+ // options.hash에는 Handlebars에서 넘긴 named parameter들(location=location 등)이 들어있음
+ return i18next.t(key, options.hash || {});
+ });
+
+export { transporter, loadTemplate }; \ No newline at end of file
diff --git a/lib/mail/sendEmail.ts b/lib/mail/sendEmail.ts
new file mode 100644
index 00000000..48cc1fbc
--- /dev/null
+++ b/lib/mail/sendEmail.ts
@@ -0,0 +1,36 @@
+import { useTranslation } from '@/i18n';
+import { transporter, loadTemplate } from './mailer';
+import handlebars from 'handlebars';
+
+interface SendEmailOptions {
+ to: string;
+ subject: string;
+ template: string; // 템플릿 파일명(확장자 제외)
+ context: Record<string, any>; // 템플릿에 주입할 데이터
+ attachments?: { // NodeMailer "Attachment" 타입
+ filename?: string
+ path?: string
+ content?: Buffer | string
+ // ...
+ }[]
+}
+
+export async function sendEmail({ to, subject, template, context, attachments = []}: SendEmailOptions) {
+ const { t, i18n } = await useTranslation(context.language ?? "en", "translation");
+
+ handlebars.registerHelper("t", function (key: string, options: any) {
+ // 여기서 i18n은 로컬 인스턴스
+ return i18n.t(key, options.hash || {});
+ });
+
+ const html = loadTemplate(template, context);
+
+ await transporter.sendMail({
+ from: 'EVCP" <dujin.kim@dtsolution.co.kr>',
+ to,
+ subject,
+ html,
+ attachments
+ });
+}
+
diff --git a/lib/mail/templates/admin-created.hbs b/lib/mail/templates/admin-created.hbs
new file mode 100644
index 00000000..7be7f15d
--- /dev/null
+++ b/lib/mail/templates/admin-created.hbs
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>{{t "adminCreated.title" lng=language}}</title>
+ <style>
+ /* 간단한 스타일 예시 */
+ body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 16px;
+ background-color: #f5f5f5;
+ }
+ .container {
+ max-width: 600px;
+ margin: 0 auto;
+ background-color: #ffffff;
+ padding: 24px;
+ border-radius: 8px;
+ }
+ h1 {
+ font-size: 20px;
+ margin-bottom: 16px;
+ }
+ p {
+ font-size: 14px;
+ line-height: 1.6;
+ }
+ .btn {
+ display: inline-block;
+ margin-top: 16px;
+ padding: 12px 24px;
+ background-color: #1D4ED8;
+ color: #ffffff !important;
+ text-decoration: none;
+ border-radius: 4px;
+ }
+ .footer {
+ margin-top: 24px;
+ font-size: 12px;
+ color: #888888;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container">
+ <!-- 상단 로고/타이틀 영역 -->
+ <div style="text-align: center;">
+ <!-- 필요 시 로고 이미지 -->
+ <!-- <img src="https://your-logo-url.com/logo.png" alt="EVCP" width="120" /> -->
+ </div>
+
+ <h1>{{t "adminCreated.title" lng=language}}</h1>
+
+ <p>
+ {{t "adminCreated.greeting" lng=language}}, <strong>{{name}}</strong>.
+ </p>
+
+ <p>
+ {{t "adminCreated.body1" lng=language}}
+ </p>
+
+ <p>
+ <a class="btn" href="{{loginUrl}}" target="_blank">{{t "adminCreated.loginCTA" lng=language}}</a>
+ </p>
+
+ <p>
+ {{t "adminCreated.supportMsg" lng=language}}
+ </p>
+
+ <div class="footer">
+ <p>
+ {{t "adminCreated.footerDisclaimer" lng=language}}
+ </p>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/admin-email-changed.hbs b/lib/mail/templates/admin-email-changed.hbs
new file mode 100644
index 00000000..7b8ca473
--- /dev/null
+++ b/lib/mail/templates/admin-email-changed.hbs
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>{{t "adminEmailChanged.title" lng=language}}</title>
+ <style>
+ /* 간단한 스타일 예시 */
+ body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 16px;
+ background-color: #f5f5f5;
+ }
+ .container {
+ max-width: 600px;
+ margin: 0 auto;
+ background-color: #ffffff;
+ padding: 24px;
+ border-radius: 8px;
+ }
+ h1 {
+ font-size: 20px;
+ margin-bottom: 16px;
+ }
+ p {
+ font-size: 14px;
+ line-height: 1.6;
+ }
+ .btn {
+ display: inline-block;
+ margin-top: 16px;
+ padding: 12px 24px;
+ background-color: #1D4ED8;
+ color: #ffffff !important;
+ text-decoration: none;
+ border-radius: 4px;
+ }
+ .footer {
+ margin-top: 24px;
+ font-size: 12px;
+ color: #888888;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container">
+ <!-- 상단 로고/타이틀 영역 -->
+ <div style="text-align: center;">
+ <!-- 필요 시 로고 이미지 -->
+ <!-- <img src="https://your-logo-url.com/logo.png" alt="EVCP" width="120" /> -->
+ </div>
+
+ <!-- 메일 제목 -->
+ <h1>{{t "adminEmailChanged.title" lng=language}}</h1>
+
+ <!-- 인사말 -->
+ <p>
+ {{t "adminEmailChanged.greeting" lng=language}}, <strong>{{name}}</strong>.
+ </p>
+
+ <!-- 이전 이메일 / 새 이메일 안내 -->
+ <p>
+ {{t "adminEmailChanged.body.intro" lng=language}}
+ </p>
+ <p>
+ <strong>{{t "adminEmailChanged.body.oldEmail" lng=language}}:</strong> {{oldEmail}}<br />
+ <strong>{{t "adminEmailChanged.body.newEmail" lng=language}}:</strong> {{newEmail}}
+ </p>
+
+ <!-- 버튼(로그인 / 대시보드 등) -->
+ <p>
+ <a class="btn" href="{{loginUrl}}" target="_blank">
+ {{t "adminEmailChanged.loginCTA" lng=language}}
+ </a>
+ </p>
+
+ <!-- 도움 요청 문구 -->
+ <p>
+ {{t "adminEmailChanged.supportMsg" lng=language}}
+ </p>
+
+ <!-- 푸터 -->
+ <div class="footer">
+ <p>
+ {{t "adminEmailChanged.footerDisclaimer" lng=language}}
+ </p>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/otp.hbs b/lib/mail/templates/otp.hbs
new file mode 100644
index 00000000..adeda416
--- /dev/null
+++ b/lib/mail/templates/otp.hbs
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+ <title>{{subject}}</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ background: #f9fafb;
+ color: #111827;
+ padding: 20px;
+ }
+ .container {
+ max-width: 480px;
+ margin: 0 auto;
+ background: #ffffff;
+ border: 1px solid #e5e7eb;
+ border-radius: 6px;
+ padding: 24px;
+ }
+ h1 {
+ font-size: 20px;
+ margin-bottom: 8px;
+ color: #111827;
+ }
+ p {
+ line-height: 1.5;
+ margin-bottom: 16px;
+ }
+ .code {
+ display: inline-block;
+ font-size: 24px;
+ font-weight: bold;
+ letter-spacing: 2px;
+ margin: 12px 0;
+ background: #f3f4f6;
+ padding: 8px 16px;
+ border-radius: 4px;
+ }
+ a {
+ color: #3b82f6;
+ text-decoration: none;
+ }
+ .footer {
+ font-size: 12px;
+ color: #6b7280;
+ margin-top: 24px;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container">
+ <h1>{{t "verifyYourEmailTitle"}}</h1>
+ <p>{{t "greeting"}}, {{name}}</p>
+
+ <p>
+ {{t "receivedSignInAttempt" location=location}}
+ </p>
+
+ <p>
+ {{t "enterCodeInstruction"}}
+ </p>
+
+ <p class="code">{{otp}}</p>
+
+ <p>
+ <a href="{{verificationUrl}}">{{verificationUrl}}</a>
+ </p>
+
+
+ <div class="footer">
+ {{t "securityWarning"}}
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/rfq-invite.hbs b/lib/mail/templates/rfq-invite.hbs
new file mode 100644
index 00000000..25bd96eb
--- /dev/null
+++ b/lib/mail/templates/rfq-invite.hbs
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>{{t "rfqInvite.title" lng=language}} #{{rfqCode}}</title>
+ <style>
+ /* 간단한 스타일 예시 */
+ body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 16px;
+ background-color: #f5f5f5;
+ }
+ .container {
+ max-width: 600px;
+ margin: 0 auto;
+ background-color: #ffffff;
+ padding: 24px;
+ border-radius: 8px;
+ }
+ h1 {
+ font-size: 20px;
+ margin-bottom: 16px;
+ }
+ p {
+ font-size: 14px;
+ line-height: 1.6;
+ }
+ ul {
+ margin-left: 20px;
+ }
+ li {
+ font-size: 14px;
+ line-height: 1.6;
+ }
+ .btn {
+ display: inline-block;
+ margin-top: 16px;
+ padding: 12px 24px;
+ background-color: #1D4ED8;
+ color: #ffffff !important;
+ text-decoration: none;
+ border-radius: 4px;
+ }
+ .footer {
+ margin-top: 24px;
+ font-size: 12px;
+ color: #888888;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container">
+ <!-- 상단 로고/타이틀 영역 -->
+ <div style="text-align: center;">
+ <!-- 필요 시 로고 이미지 -->
+ <!-- <img src="https://your-logo-url.com/logo.png" alt="EVCP" width="120" /> -->
+ </div>
+
+ <!-- 메인 타이틀: RFQ 초대 -->
+ <h1>
+ {{t "rfqInvite.heading" lng=language}}
+ #{{rfqCode}}
+ </h1>
+
+ <!-- 벤더에게 인사말 -->
+ <p>
+ {{t "rfqInvite.greeting" lng=language}}, <strong>Vendor #{{vendorId}}</strong>.
+ </p>
+
+ <!-- 프로젝트/RFQ 정보 -->
+ <p>
+ {{t "rfqInvite.bodyIntro" lng=language}}
+ <br />
+ <strong>{{t "rfqInvite.projectName" lng=language}}:</strong> {{projectName}}<br />
+ <strong>{{t "rfqInvite.projectCode" lng=language}}:</strong> {{projectCode}}<br />
+ <strong>{{t "rfqInvite.dueDate" lng=language}}:</strong> {{dueDate}}<br />
+ <strong>{{t "rfqInvite.description" lng=language}}:</strong> {{description}}
+ </p>
+
+ <!-- 아이템 목록 -->
+ <p>
+ {{t "rfqInvite.itemListTitle" lng=language}}
+ </p>
+ <ul>
+ {{#each items}}
+ <li>
+ <strong>{{this.itemCode}}</strong>
+ ({{this.quantity}} {{this.uom}})
+ - {{this.description}}
+ </li>
+ {{/each}}
+ </ul>
+
+ <!-- 로그인/접속 안내 -->
+ <p>
+ {{t "rfqInvite.moreDetail" lng=language}}
+ </p>
+ <a class="btn" href="{{loginUrl}}" target="_blank">
+ {{t "rfqInvite.viewButton" lng=language}}
+ </a>
+
+ <!-- 기타 안내 문구 -->
+ <p>
+ {{t "rfqInvite.supportMsg" lng=language}}
+ </p>
+
+ <!-- 푸터 -->
+ <div class="footer">
+ <p>
+ {{t "rfqInvite.footerDisclaimer" lng=language}}
+ </p>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/vendor-active.hbs b/lib/mail/templates/vendor-active.hbs
new file mode 100644
index 00000000..6458e2fb
--- /dev/null
+++ b/lib/mail/templates/vendor-active.hbs
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>벤더 등록이 완료되었습니다</title>
+ <style>
+ body { font-family: 'Malgun Gothic', sans-serif; line-height: 1.6; }
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
+ .header { background-color: #f5f5f5; padding: 10px; text-align: center; }
+ .content { padding: 20px 0; }
+ .vendor-code { font-size: 18px; font-weight: bold; background-color: #f0f0f0;
+ padding: 10px; margin: 15px 0; text-align: center; }
+ .button { display: inline-block; background-color: #28a745; color: white;
+ padding: 10px 20px; text-decoration: none; border-radius: 4px; }
+ .footer { margin-top: 20px; font-size: 12px; color: #777; }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <div class="header">
+ <h2>벤더 등록이 완료되었습니다</h2>
+ </div>
+
+ <div class="content">
+ <p>{{vendorName}} 귀하,</p>
+
+ <p>축하합니다! 귀사의 벤더 등록이 완료되었으며 벤더 정보가 당사 시스템에 성공적으로 등록되었습니다.</p>
+
+ <p>귀사의 벤더 코드는 다음과 같습니다:</p>
+ <div class="vendor-code">{{vendorCode}}</div>
+
+ <p>향후 모든 의사소통 및 거래 시 이 벤더 코드를 사용해 주십시오. 이제 벤더 포털에 접속하여 계정 관리, 발주서 확인 및 인보이스 제출을 할 수 있습니다.</p>
+
+ <p style="text-align: center; margin: 25px 0;">
+ <a href="{{portalUrl}}" class="button">벤더 포털 접속</a>
+ </p>
+
+ <p>벤더 계정에 관한 질문이나 도움이 필요하시면 당사 벤더 관리팀에 문의해 주십시오.</p>
+
+ <p>파트너십에 감사드립니다.</p>
+
+ <p>감사합니다.<br>
+ eVCP 팀</p>
+ </div>
+
+ <div class="footer">
+ <p>이 메시지는 자동으로 발송되었습니다. 이 이메일에 회신하지 마십시오.</p>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/vendor-pq-comment.hbs b/lib/mail/templates/vendor-pq-comment.hbs
new file mode 100644
index 00000000..b60deedc
--- /dev/null
+++ b/lib/mail/templates/vendor-pq-comment.hbs
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>PQ Review Comments</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ line-height: 1.6;
+ color: #333;
+ margin: 0;
+ padding: 0;
+ }
+ .container {
+ max-width: 600px;
+ margin: 0 auto;
+ padding: 20px;
+ }
+ .header {
+ text-align: center;
+ padding: 20px 0;
+ border-bottom: 1px solid #eee;
+ }
+ .content {
+ padding: 20px 0;
+ }
+ .footer {
+ text-align: center;
+ padding: 20px 0;
+ font-size: 12px;
+ color: #999;
+ border-top: 1px solid #eee;
+ }
+ .btn {
+ display: inline-block;
+ padding: 10px 20px;
+ font-size: 16px;
+ color: #fff;
+ background-color: #0071bc;
+ text-decoration: none;
+ border-radius: 4px;
+ margin: 20px 0;
+ }
+ .comment-section {
+ margin: 20px 0;
+ padding: 15px;
+ background-color: #f9f9f9;
+ border-left: 4px solid #0071bc;
+ }
+ .comment-item {
+ margin-bottom: 15px;
+ padding-bottom: 15px;
+ border-bottom: 1px solid #eee;
+ }
+ .comment-item:last-child {
+ border-bottom: none;
+ }
+ .comment-code {
+ font-weight: bold;
+ color: #0071bc;
+ display: inline-block;
+ min-width: 60px;
+ }
+ .comment-title {
+ font-weight: bold;
+ color: #333;
+ }
+ .important {
+ color: #d14;
+ font-weight: bold;
+ }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <div class="header">
+ <h1>PQ Review Comments</h1>
+ </div>
+
+ <div class="content">
+ <p>Dear {{name}} ({{vendorCode}}),</p>
+
+ <p>Thank you for submitting your PQ information. Our review team has completed the initial review and has requested some changes or additional information.</p>
+
+ <p><span class="important">Action Required:</span> Please log in to your account and update your PQ submission based on the comments below.</p>
+
+ {{#if hasGeneralComment}}
+ <div class="comment-section">
+ <h3>General Comments:</h3>
+ <p>{{generalComment}}</p>
+ </div>
+ {{/if}}
+
+ <div class="comment-section">
+ <h3>Specific Item Comments ({{commentCount}}):</h3>
+ {{#each comments}}
+ <div class="comment-item">
+ <div>
+ <span class="comment-code">{{code}}</span>
+ <span class="comment-title">{{checkPoint}}</span>
+ </div>
+ <p>{{text}}</p>
+ </div>
+ {{/each}}
+ </div>
+
+ <p>Please review these comments and make the necessary updates to your PQ submission. Once you have made the requested changes, you can resubmit your PQ for further review.</p>
+
+ <div style="text-align: center;">
+ <a href="{{loginUrl}}" class="btn">Log in to update your PQ</a>
+ </div>
+
+ <p>If you have any questions or need assistance, please contact our support team.</p>
+
+ <p>Thank you for your cooperation.</p>
+
+ <p>Best regards,<br>
+ PQ Review Team</p>
+ </div>
+
+ <div class="footer">
+ <p>This is an automated email. Please do not reply to this message.</p>
+ <p>&copy; {{currentYear}} Your Company Name. All rights reserved.</p>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/lib/mail/templates/vendor-pq-status.hbs b/lib/mail/templates/vendor-pq-status.hbs
new file mode 100644
index 00000000..541a6137
--- /dev/null
+++ b/lib/mail/templates/vendor-pq-status.hbs
@@ -0,0 +1,48 @@
+<!-- file: templates/vendor-pq-status.hbs -->
+
+<html>
+ <body style="font-family: sans-serif; margin: 0; padding: 0;">
+ <table width="100%" cellspacing="0" cellpadding="20" style="background-color: #f7f7f7;">
+ <tr>
+ <td>
+ <table width="600" cellspacing="0" cellpadding="20" style="background-color: #ffffff; margin: 0 auto;">
+ <tr>
+ <td style="text-align: center;">
+ <h1 style="margin-bottom: 0.5rem;">Vendor PQ Status Update</h1>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <p>Hello {{name}},</p>
+ <p>
+ Your vendor status has been updated to
+ <strong>{{status}}</strong>.
+ </p>
+ <p>
+ You can log in to see details and take further action:
+ <br />
+ <a href="{{loginUrl}}" style="color: #007bff; text-decoration: underline;">
+ Go to Portal
+ </a>
+ </p>
+ <p>
+ If you have any questions, feel free to contact us.
+ </p>
+ <p>Thank you,<br/>
+ The PQ Team
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td style="text-align: center; border-top: 1px solid #eee;">
+ <small style="color: #999;">
+ &copy; 2023 MyCompany
+ </small>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html> \ No newline at end of file