238 lines
11 KiB
JavaScript
238 lines
11 KiB
JavaScript
/**
|
|
* Email Service using Resend
|
|
*
|
|
* 환경변수 필요:
|
|
* - RESEND_API_KEY: Resend API 키 (https://resend.com에서 발급)
|
|
* - RESEND_FROM_EMAIL: 발신 이메일 (도메인 인증 전에는 'onboarding@resend.dev' 사용)
|
|
* - FRONTEND_URL: 프론트엔드 URL (인증 링크용)
|
|
*/
|
|
|
|
const { Resend } = require('resend');
|
|
|
|
// Resend 인스턴스 - API 키가 없으면 null (이메일 기능 비활성화)
|
|
let resend = null;
|
|
const RESEND_API_KEY = process.env.RESEND_API_KEY;
|
|
|
|
if (RESEND_API_KEY && RESEND_API_KEY !== 'your-resend-api-key') {
|
|
resend = new Resend(RESEND_API_KEY);
|
|
console.log('📧 이메일 서비스 활성화됨 (Resend)');
|
|
} else {
|
|
console.warn('⚠️ 이메일 서비스 비활성화됨: RESEND_API_KEY가 설정되지 않았습니다.');
|
|
console.warn(' 이메일 인증 기능을 사용하려면 .env에 RESEND_API_KEY를 설정하세요.');
|
|
}
|
|
|
|
// 기본 발신 이메일 (도메인 미인증 시 Resend 기본 도메인 사용)
|
|
const FROM_EMAIL = process.env.RESEND_FROM_EMAIL || 'CastAD <onboarding@resend.dev>';
|
|
const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:3000';
|
|
|
|
/**
|
|
* 이메일 인증 메일 발송
|
|
*/
|
|
async function sendVerificationEmail(to, name, verificationToken) {
|
|
if (!resend) {
|
|
console.warn('이메일 발송 건너뜀 (서비스 비활성화):', to);
|
|
return { success: false, error: '이메일 서비스가 설정되지 않았습니다', disabled: true };
|
|
}
|
|
|
|
const verifyUrl = `${FRONTEND_URL}/verify-email?token=${verificationToken}`;
|
|
|
|
try {
|
|
const { data, error } = await resend.emails.send({
|
|
from: FROM_EMAIL,
|
|
to: [to],
|
|
subject: '[CastAD] 이메일 인증을 완료해주세요',
|
|
html: `
|
|
<div style="font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px;">
|
|
<div style="text-align: center; margin-bottom: 40px;">
|
|
<h1 style="color: #6366f1; margin: 0;">CastAD</h1>
|
|
<p style="color: #64748b; margin-top: 8px;">AI 펜션 마케팅 영상 제작</p>
|
|
</div>
|
|
|
|
<div style="background: #f8fafc; border-radius: 12px; padding: 32px;">
|
|
<h2 style="margin: 0 0 16px 0; color: #1e293b;">안녕하세요, ${name || '고객'}님!</h2>
|
|
<p style="color: #475569; line-height: 1.6; margin: 0 0 24px 0;">
|
|
CastAD 회원가입을 환영합니다. 아래 버튼을 클릭하여 이메일 인증을 완료해주세요.
|
|
</p>
|
|
|
|
<div style="text-align: center; margin: 32px 0;">
|
|
<a href="${verifyUrl}"
|
|
style="display: inline-block; background: #6366f1; color: white; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600;">
|
|
이메일 인증하기
|
|
</a>
|
|
</div>
|
|
|
|
<p style="color: #64748b; font-size: 14px; margin: 24px 0 0 0;">
|
|
버튼이 작동하지 않으면 아래 링크를 브라우저에 복사해주세요:<br>
|
|
<a href="${verifyUrl}" style="color: #6366f1; word-break: break-all;">${verifyUrl}</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 32px; color: #94a3b8; font-size: 12px;">
|
|
<p>이 이메일은 CastAD 회원가입 요청으로 발송되었습니다.</p>
|
|
<p>본인이 요청하지 않았다면 이 이메일을 무시해주세요.</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
});
|
|
|
|
if (error) {
|
|
console.error('이메일 발송 실패:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
console.log('인증 이메일 발송 완료:', data.id);
|
|
return { success: true, id: data.id };
|
|
} catch (err) {
|
|
console.error('이메일 발송 예외:', err);
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 비밀번호 재설정 이메일 발송
|
|
*/
|
|
async function sendPasswordResetEmail(to, name, resetToken) {
|
|
if (!resend) {
|
|
console.warn('이메일 발송 건너뜀 (서비스 비활성화):', to);
|
|
return { success: false, error: '이메일 서비스가 설정되지 않았습니다', disabled: true };
|
|
}
|
|
|
|
const resetUrl = `${FRONTEND_URL}/reset-password?token=${resetToken}`;
|
|
|
|
try {
|
|
const { data, error } = await resend.emails.send({
|
|
from: FROM_EMAIL,
|
|
to: [to],
|
|
subject: '[CastAD] 비밀번호 재설정 안내',
|
|
html: `
|
|
<div style="font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px;">
|
|
<div style="text-align: center; margin-bottom: 40px;">
|
|
<h1 style="color: #6366f1; margin: 0;">CastAD</h1>
|
|
<p style="color: #64748b; margin-top: 8px;">AI 펜션 마케팅 영상 제작</p>
|
|
</div>
|
|
|
|
<div style="background: #f8fafc; border-radius: 12px; padding: 32px;">
|
|
<h2 style="margin: 0 0 16px 0; color: #1e293b;">비밀번호 재설정</h2>
|
|
<p style="color: #475569; line-height: 1.6; margin: 0 0 24px 0;">
|
|
${name || '고객'}님, 비밀번호 재설정을 요청하셨습니다.<br>
|
|
아래 버튼을 클릭하여 새 비밀번호를 설정해주세요.
|
|
</p>
|
|
|
|
<div style="text-align: center; margin: 32px 0;">
|
|
<a href="${resetUrl}"
|
|
style="display: inline-block; background: #6366f1; color: white; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600;">
|
|
비밀번호 재설정하기
|
|
</a>
|
|
</div>
|
|
|
|
<div style="background: #fef3c7; border-radius: 8px; padding: 16px; margin: 24px 0;">
|
|
<p style="color: #92400e; font-size: 14px; margin: 0;">
|
|
⚠️ 이 링크는 1시간 동안만 유효합니다.
|
|
</p>
|
|
</div>
|
|
|
|
<p style="color: #64748b; font-size: 14px; margin: 0;">
|
|
버튼이 작동하지 않으면 아래 링크를 브라우저에 복사해주세요:<br>
|
|
<a href="${resetUrl}" style="color: #6366f1; word-break: break-all;">${resetUrl}</a>
|
|
</p>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 32px; color: #94a3b8; font-size: 12px;">
|
|
<p>본인이 비밀번호 재설정을 요청하지 않았다면 이 이메일을 무시해주세요.</p>
|
|
<p>계정은 안전하게 보호됩니다.</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
});
|
|
|
|
if (error) {
|
|
console.error('비밀번호 재설정 이메일 발송 실패:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
console.log('비밀번호 재설정 이메일 발송 완료:', data.id);
|
|
return { success: true, id: data.id };
|
|
} catch (err) {
|
|
console.error('이메일 발송 예외:', err);
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 환영 이메일 발송 (인증 완료 후)
|
|
*/
|
|
async function sendWelcomeEmail(to, name) {
|
|
if (!resend) {
|
|
console.warn('이메일 발송 건너뜀 (서비스 비활성화):', to);
|
|
return { success: false, error: '이메일 서비스가 설정되지 않았습니다', disabled: true };
|
|
}
|
|
|
|
try {
|
|
const { data, error } = await resend.emails.send({
|
|
from: FROM_EMAIL,
|
|
to: [to],
|
|
subject: '[CastAD] 가입을 환영합니다! 🎉',
|
|
html: `
|
|
<div style="font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif; max-width: 600px; margin: 0 auto; padding: 40px 20px;">
|
|
<div style="text-align: center; margin-bottom: 40px;">
|
|
<h1 style="color: #6366f1; margin: 0;">CastAD</h1>
|
|
<p style="color: #64748b; margin-top: 8px;">AI 펜션 마케팅 영상 제작</p>
|
|
</div>
|
|
|
|
<div style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); border-radius: 12px; padding: 32px; color: white;">
|
|
<h2 style="margin: 0 0 16px 0;">🎉 환영합니다, ${name || '고객'}님!</h2>
|
|
<p style="line-height: 1.6; margin: 0; opacity: 0.9;">
|
|
CastAD 가입이 완료되었습니다.<br>
|
|
이제 AI가 만드는 펜션 마케팅 영상을 경험해보세요!
|
|
</p>
|
|
</div>
|
|
|
|
<div style="margin-top: 32px; padding: 24px; background: #f8fafc; border-radius: 12px;">
|
|
<h3 style="margin: 0 0 16px 0; color: #1e293b;">시작하기</h3>
|
|
<ul style="color: #475569; line-height: 2; padding-left: 20px; margin: 0;">
|
|
<li>펜션 정보를 등록하세요</li>
|
|
<li>사진을 업로드하고 AI 영상을 생성하세요</li>
|
|
<li>YouTube에 바로 업로드하세요</li>
|
|
</ul>
|
|
|
|
<div style="text-align: center; margin-top: 24px;">
|
|
<a href="${FRONTEND_URL}/app"
|
|
style="display: inline-block; background: #6366f1; color: white; padding: 14px 32px; border-radius: 8px; text-decoration: none; font-weight: 600;">
|
|
CastAD 시작하기
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 32px; color: #94a3b8; font-size: 12px;">
|
|
<p>문의사항이 있으시면 언제든 연락주세요.</p>
|
|
</div>
|
|
</div>
|
|
`
|
|
});
|
|
|
|
if (error) {
|
|
console.error('환영 이메일 발송 실패:', error);
|
|
return { success: false, error: error.message };
|
|
}
|
|
|
|
return { success: true, id: data.id };
|
|
} catch (err) {
|
|
console.error('이메일 발송 예외:', err);
|
|
return { success: false, error: err.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 이메일 서비스 활성화 여부 확인
|
|
*/
|
|
function isEmailServiceEnabled() {
|
|
return resend !== null;
|
|
}
|
|
|
|
module.exports = {
|
|
sendVerificationEmail,
|
|
sendPasswordResetEmail,
|
|
sendWelcomeEmail,
|
|
isEmailServiceEnabled
|
|
};
|