diff options
| author | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
| commit | 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch) | |
| tree | 8a5587f10ca55b162d7e3254cb088b323a34c41b /lib/mail | |
initial commit
Diffstat (limited to 'lib/mail')
| -rw-r--r-- | lib/mail/mailer.ts | 31 | ||||
| -rw-r--r-- | lib/mail/sendEmail.ts | 36 | ||||
| -rw-r--r-- | lib/mail/templates/admin-created.hbs | 78 | ||||
| -rw-r--r-- | lib/mail/templates/admin-email-changed.hbs | 90 | ||||
| -rw-r--r-- | lib/mail/templates/otp.hbs | 77 | ||||
| -rw-r--r-- | lib/mail/templates/rfq-invite.hbs | 116 | ||||
| -rw-r--r-- | lib/mail/templates/vendor-active.hbs | 51 | ||||
| -rw-r--r-- | lib/mail/templates/vendor-pq-comment.hbs | 128 | ||||
| -rw-r--r-- | lib/mail/templates/vendor-pq-status.hbs | 48 |
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>© {{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;"> + © 2023 MyCompany + </small> + </td> + </tr> + </table> + </td> + </tr> + </table> + </body> +</html>
\ No newline at end of file |
