CASTAD-v0.1/server/emailService.js

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
};