메일관리 기능 구현 완료
This commit is contained in:
@@ -12,6 +12,7 @@ import { mailSentHistoryService } from './mailSentHistoryService';
|
||||
export interface SendMailRequest {
|
||||
accountId: string;
|
||||
templateId?: string;
|
||||
modifiedTemplateComponents?: any[]; // 🎯 프론트엔드에서 수정된 템플릿 컴포넌트
|
||||
to: string[]; // 받는 사람
|
||||
cc?: string[]; // 참조 (Carbon Copy)
|
||||
bcc?: string[]; // 숨은참조 (Blind Carbon Copy)
|
||||
@@ -52,15 +53,29 @@ class MailSendSimpleService {
|
||||
throw new Error('비활성 상태의 계정입니다.');
|
||||
}
|
||||
|
||||
// 3. HTML 생성 (템플릿 또는 커스텀)
|
||||
htmlContent = request.customHtml || '';
|
||||
|
||||
if (!htmlContent && request.templateId) {
|
||||
// 3. HTML 생성 (템플릿 + 추가 메시지 병합)
|
||||
if (request.templateId) {
|
||||
// 템플릿 사용
|
||||
const template = await mailTemplateFileService.getTemplateById(request.templateId);
|
||||
if (!template) {
|
||||
throw new Error('템플릿을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 🎯 수정된 컴포넌트가 있으면 덮어쓰기
|
||||
if (request.modifiedTemplateComponents && request.modifiedTemplateComponents.length > 0) {
|
||||
console.log('✏️ 수정된 템플릿 컴포넌트 사용:', request.modifiedTemplateComponents.length);
|
||||
template.components = request.modifiedTemplateComponents;
|
||||
}
|
||||
|
||||
htmlContent = this.renderTemplate(template, request.variables);
|
||||
|
||||
// 템플릿 + 추가 메시지 병합
|
||||
if (request.customHtml && request.customHtml.trim()) {
|
||||
htmlContent = this.mergeTemplateAndCustomContent(htmlContent, request.customHtml);
|
||||
}
|
||||
} else {
|
||||
// 직접 작성
|
||||
htmlContent = request.customHtml || '';
|
||||
}
|
||||
|
||||
if (!htmlContent) {
|
||||
@@ -261,13 +276,25 @@ class MailSendSimpleService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 렌더링 (간단 버전)
|
||||
* 템플릿 렌더링 (일반 메일 양식)
|
||||
*/
|
||||
private renderTemplate(
|
||||
template: any,
|
||||
variables?: Record<string, string>
|
||||
): string {
|
||||
let html = '<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">';
|
||||
// 일반적인 메일 레이아웃 (전체 너비, 그림자 없음)
|
||||
let html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;">
|
||||
<table role="presentation" style="width: 100%; border-collapse: collapse; background-color: #ffffff;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
`;
|
||||
|
||||
template.components.forEach((component: any) => {
|
||||
switch (component.type) {
|
||||
@@ -276,20 +303,23 @@ class MailSendSimpleService {
|
||||
if (variables) {
|
||||
content = this.replaceVariables(content, variables);
|
||||
}
|
||||
html += `<p style="margin: 16px 0; color: ${component.color || '#333'}; font-size: ${component.fontSize || '14px'};">${content}</p>`;
|
||||
// 텍스트는 왼쪽 정렬, 적절한 줄간격
|
||||
html += `<div style="margin: 0 0 20px 0; color: ${component.color || '#333'}; font-size: ${component.fontSize || '15px'}; line-height: 1.6; text-align: left;">${content}</div>`;
|
||||
break;
|
||||
case 'button':
|
||||
let buttonText = component.text || 'Button';
|
||||
if (variables) {
|
||||
buttonText = this.replaceVariables(buttonText, variables);
|
||||
}
|
||||
html += `<div style="text-align: center; margin: 24px 0;">
|
||||
<a href="${component.url || '#'}" style="display: inline-block; padding: 12px 24px; background-color: ${component.backgroundColor || '#007bff'}; color: ${component.textColor || '#fff'}; text-decoration: none; border-radius: 4px;">${buttonText}</a>
|
||||
// 버튼은 왼쪽 정렬 (text-align 제거)
|
||||
html += `<div style="margin: 30px 0; text-align: left;">
|
||||
<a href="${component.url || '#'}" style="display: inline-block; padding: 14px 28px; background-color: ${component.backgroundColor || '#007bff'}; color: ${component.textColor || '#fff'}; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 15px;">${buttonText}</a>
|
||||
</div>`;
|
||||
break;
|
||||
case 'image':
|
||||
html += `<div style="text-align: center; margin: 16px 0;">
|
||||
<img src="${component.src}" alt="${component.alt || ''}" style="max-width: 100%; height: auto;" />
|
||||
// 이미지는 왼쪽 정렬
|
||||
html += `<div style="margin: 20px 0; text-align: left;">
|
||||
<img src="${component.src}" alt="${component.alt || ''}" style="max-width: 100%; height: auto; display: block; border-radius: 4px;" />
|
||||
</div>`;
|
||||
break;
|
||||
case 'spacer':
|
||||
@@ -298,7 +328,13 @@ class MailSendSimpleService {
|
||||
}
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += `
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -320,6 +356,52 @@ class MailSendSimpleService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿과 추가 메시지 병합
|
||||
* 템플릿 HTML의 body 태그 끝 부분에 추가 메시지를 삽입
|
||||
*/
|
||||
private mergeTemplateAndCustomContent(templateHtml: string, customContent: string): string {
|
||||
// customContent에 HTML 태그가 없으면 기본 스타일 적용
|
||||
let formattedCustomContent = customContent;
|
||||
if (!customContent.includes('<')) {
|
||||
// 일반 텍스트인 경우 단락으로 변환
|
||||
const paragraphs = customContent
|
||||
.split('\n\n')
|
||||
.filter((p) => p.trim())
|
||||
.map((p) => `<p style="margin: 16px 0; line-height: 1.6;">${p.replace(/\n/g, '<br>')}</p>`)
|
||||
.join('');
|
||||
|
||||
formattedCustomContent = `
|
||||
<div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid #e5e7eb;">
|
||||
${paragraphs}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// 이미 HTML인 경우 구분선만 추가
|
||||
formattedCustomContent = `
|
||||
<div style="margin-top: 32px; padding-top: 24px; border-top: 1px solid #e5e7eb;">
|
||||
${customContent}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// </body> 또는 </div> 태그 앞에 삽입
|
||||
if (templateHtml.includes('</body>')) {
|
||||
return templateHtml.replace('</body>', `${formattedCustomContent}</body>`);
|
||||
} else if (templateHtml.includes('</div>')) {
|
||||
// 마지막 </div> 앞에 삽입
|
||||
const lastDivIndex = templateHtml.lastIndexOf('</div>');
|
||||
return (
|
||||
templateHtml.substring(0, lastDivIndex) +
|
||||
formattedCustomContent +
|
||||
templateHtml.substring(lastDivIndex)
|
||||
);
|
||||
} else {
|
||||
// 태그가 없으면 단순 결합
|
||||
return templateHtml + formattedCustomContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SMTP 연결 테스트
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user