2025-10-01 16:15:53 +09:00
|
|
|
import { Request, Response } from 'express';
|
|
|
|
|
import { mailSendSimpleService } from '../services/mailSendSimpleService';
|
|
|
|
|
|
|
|
|
|
export class MailSendSimpleController {
|
|
|
|
|
/**
|
2025-10-02 18:22:58 +09:00
|
|
|
* 메일 발송 (단건 또는 소규모) - 첨부파일 지원
|
2025-10-01 16:15:53 +09:00
|
|
|
*/
|
|
|
|
|
async sendMail(req: Request, res: Response) {
|
|
|
|
|
try {
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.log('📧 메일 발송 요청 수신:', {
|
|
|
|
|
// accountId: req.body.accountId,
|
|
|
|
|
// to: req.body.to,
|
|
|
|
|
// cc: req.body.cc,
|
|
|
|
|
// bcc: req.body.bcc,
|
|
|
|
|
// subject: req.body.subject,
|
|
|
|
|
// attachments: req.files ? (req.files as Express.Multer.File[]).length : 0,
|
|
|
|
|
// });
|
2025-10-02 18:22:58 +09:00
|
|
|
|
|
|
|
|
// FormData에서 JSON 문자열 파싱
|
|
|
|
|
const accountId = req.body.accountId;
|
|
|
|
|
const templateId = req.body.templateId;
|
2025-10-13 15:17:34 +09:00
|
|
|
const modifiedTemplateComponents = req.body.modifiedTemplateComponents
|
|
|
|
|
? JSON.parse(req.body.modifiedTemplateComponents)
|
|
|
|
|
: undefined; // 🎯 수정된 템플릿 컴포넌트
|
2025-10-02 18:22:58 +09:00
|
|
|
const to = req.body.to ? JSON.parse(req.body.to) : [];
|
|
|
|
|
const cc = req.body.cc ? JSON.parse(req.body.cc) : undefined;
|
|
|
|
|
const bcc = req.body.bcc ? JSON.parse(req.body.bcc) : undefined;
|
|
|
|
|
const subject = req.body.subject;
|
|
|
|
|
const variables = req.body.variables ? JSON.parse(req.body.variables) : undefined;
|
|
|
|
|
const customHtml = req.body.customHtml;
|
2025-10-01 16:15:53 +09:00
|
|
|
|
|
|
|
|
// 필수 파라미터 검증
|
|
|
|
|
if (!accountId || !to || !Array.isArray(to) || to.length === 0) {
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.log('❌ 필수 파라미터 누락');
|
2025-10-01 16:15:53 +09:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '계정 ID와 수신자 이메일이 필요합니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!subject) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '메일 제목이 필요합니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 템플릿 또는 커스텀 HTML 중 하나는 있어야 함
|
|
|
|
|
if (!templateId && !customHtml) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '템플릿 또는 메일 내용이 필요합니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-02 18:22:58 +09:00
|
|
|
// 첨부파일 처리 (한글 파일명 지원)
|
|
|
|
|
const attachments: Array<{ filename: string; path: string; contentType?: string }> = [];
|
|
|
|
|
if (req.files && Array.isArray(req.files)) {
|
|
|
|
|
const files = req.files as Express.Multer.File[];
|
|
|
|
|
|
|
|
|
|
// 프론트엔드에서 전송한 정규화된 파일명 사용 (한글-분석.txt 방식)
|
|
|
|
|
let parsedFileNames: string[] = [];
|
|
|
|
|
if (req.body.fileNames) {
|
|
|
|
|
try {
|
|
|
|
|
parsedFileNames = JSON.parse(req.body.fileNames);
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.log('📎 프론트엔드에서 받은 파일명들:', parsedFileNames);
|
2025-10-02 18:22:58 +09:00
|
|
|
} catch (e) {
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.warn('파일명 파싱 실패, multer originalname 사용');
|
2025-10-02 18:22:58 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
files.forEach((file, index) => {
|
|
|
|
|
// 클라이언트에서 전송한 파일명 우선 사용, 없으면 multer의 originalname 사용
|
|
|
|
|
let originalName = parsedFileNames[index] || file.originalname;
|
|
|
|
|
|
|
|
|
|
// NFC 정규화 확실히 수행
|
|
|
|
|
originalName = originalName.normalize('NFC');
|
|
|
|
|
|
|
|
|
|
attachments.push({
|
|
|
|
|
filename: originalName,
|
|
|
|
|
path: file.path,
|
|
|
|
|
contentType: file.mimetype,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.log('📎 최종 첨부파일 정보:', attachments.map(a => ({
|
|
|
|
|
// filename: a.filename,
|
|
|
|
|
// path: a.path.split('/').pop()
|
|
|
|
|
// })));
|
2025-10-02 18:22:58 +09:00
|
|
|
}
|
|
|
|
|
|
2025-10-01 16:15:53 +09:00
|
|
|
// 메일 발송
|
|
|
|
|
const result = await mailSendSimpleService.sendMail({
|
|
|
|
|
accountId,
|
|
|
|
|
templateId,
|
2025-10-13 15:17:34 +09:00
|
|
|
modifiedTemplateComponents, // 🎯 수정된 템플릿 컴포넌트 전달
|
2025-10-01 16:15:53 +09:00
|
|
|
to,
|
2025-10-02 18:22:58 +09:00
|
|
|
cc,
|
|
|
|
|
bcc,
|
2025-10-01 16:15:53 +09:00
|
|
|
subject,
|
|
|
|
|
variables,
|
|
|
|
|
customHtml,
|
2025-10-02 18:22:58 +09:00
|
|
|
attachments: attachments.length > 0 ? attachments : undefined,
|
2025-10-01 16:15:53 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result,
|
|
|
|
|
message: '메일이 발송되었습니다.',
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: result.error || '메일 발송 실패',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const err = error as Error;
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '메일 발송 중 오류가 발생했습니다.',
|
|
|
|
|
error: err.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 16:06:04 +09:00
|
|
|
/**
|
|
|
|
|
* 대량 메일 발송
|
|
|
|
|
*/
|
|
|
|
|
async sendBulkMail(req: Request, res: Response) {
|
|
|
|
|
try {
|
2025-10-22 17:07:38 +09:00
|
|
|
const { accountId, templateId, customHtml, subject, recipients } = req.body;
|
2025-10-22 16:06:04 +09:00
|
|
|
|
|
|
|
|
// 필수 파라미터 검증
|
2025-10-22 17:07:38 +09:00
|
|
|
if (!accountId || !subject || !recipients || !Array.isArray(recipients)) {
|
2025-10-22 16:06:04 +09:00
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '필수 파라미터가 누락되었습니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 17:07:38 +09:00
|
|
|
// 템플릿 또는 직접 작성 중 하나는 있어야 함
|
|
|
|
|
if (!templateId && !customHtml) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '템플릿 또는 메일 내용 중 하나는 필수입니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 16:06:04 +09:00
|
|
|
if (recipients.length === 0) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '수신자가 없습니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 17:07:38 +09:00
|
|
|
// console.log(`📧 대량 발송 요청: ${recipients.length}명`);
|
2025-10-22 16:06:04 +09:00
|
|
|
|
|
|
|
|
// 대량 발송 실행
|
|
|
|
|
const result = await mailSendSimpleService.sendBulkMail({
|
|
|
|
|
accountId,
|
2025-10-22 17:07:38 +09:00
|
|
|
templateId, // 선택
|
|
|
|
|
customHtml, // 선택
|
2025-10-22 16:06:04 +09:00
|
|
|
subject,
|
|
|
|
|
recipients,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result,
|
|
|
|
|
message: `${result.success}/${result.total} 건 발송 완료`,
|
|
|
|
|
});
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const err = error as Error;
|
|
|
|
|
console.error('❌ 대량 발송 오류:', err);
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '대량 발송 중 오류가 발생했습니다.',
|
|
|
|
|
error: err.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 16:15:53 +09:00
|
|
|
/**
|
|
|
|
|
* SMTP 연결 테스트
|
|
|
|
|
*/
|
|
|
|
|
async testConnection(req: Request, res: Response) {
|
|
|
|
|
try {
|
|
|
|
|
const { accountId } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!accountId) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '계정 ID가 필요합니다.',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await mailSendSimpleService.testConnection(accountId);
|
|
|
|
|
|
|
|
|
|
return res.json(result);
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const err = error as Error;
|
|
|
|
|
return res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '연결 테스트 실패',
|
|
|
|
|
error: err.message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const mailSendSimpleController = new MailSendSimpleController();
|
|
|
|
|
|