From 13f4d075772c7913d858012f8f6bdad2924ce3d3 Mon Sep 17 00:00:00 2001 From: leeheejin Date: Wed, 22 Oct 2025 17:07:38 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EC=9D=BC=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1bb5ebfe-3f6c-4884-a043-161ae3f74f75.json | 16 + .../1d997eeb-3d61-427d-8b54-119d4372b9b3.json | 18 + .../331d95d6-3a13-4657-bc75-ab0811712eb8.json | 18 + .../375f2326-ca86-468a-bfc3-2d4c3825577b.json | 3 +- .../386e334a-df76-440c-ae8a-9bf06982fdc8.json | 3 +- .../3d411dc4-69a6-4236-b878-9693dff881be.json | 3 +- .../3e30a264-8431-44c7-96ef-eed551e66a11.json | 3 +- .../4a32bab5-364e-4037-bb00-31d2905824db.json | 16 + .../5bfb2acd-023a-4865-a738-2900179db5fb.json | 3 +- .../683c1323-1895-403a-bb9a-4e111a8909f6.json | 3 +- .../7bed27d5-dae4-4ba8-85d0-c474c4fb907a.json | 3 +- .../8990ea86-3112-4e7c-b3e0-8b494181c4e0.json | 3 +- .../9ab1e5ee-4f5e-4b79-9769-5e2a1e1ffc8e.json | 3 +- .../9d0b9fcf-cabf-4053-b6b6-6e110add22de.json | 3 +- .../a638f7d0-ee31-47fa-9f72-de66ef31ea44.json | 18 + .../b293e530-2b2d-4b8a-8081-d103fab5a13f.json | 3 +- .../cf892a77-1998-4165-bb9d-b390451465b2.json | 16 + .../e3501abc-cd31-4b20-bb02-3c7ddbe54eb8.json | 3 +- .../fd2a8b41-2e6e-4e5e-b8e8-63d31efc5082.json | 3 +- .../controllers/mailReceiveBasicController.ts | 38 +- .../controllers/mailSendSimpleController.ts | 47 +- .../src/services/mailReceiveBasicService.ts | 124 +- .../src/services/mailSendSimpleService.ts | 52 +- .../src/services/mailSentHistoryService.ts | 20 +- .../src/services/mailTemplateFileService.ts | 4 +- .../app/(main)/admin/mail/accounts/page.tsx | 10 +- .../app/(main)/admin/mail/bulk-send/page.tsx | 67 +- .../app/(main)/admin/mail/dashboard/page.tsx | 6 +- .../app/(main)/admin/mail/drafts/page.tsx | 10 +- .../app/(main)/admin/mail/receive/page.tsx | 159 ++- frontend/app/(main)/admin/mail/send/page.tsx | 94 +- frontend/app/(main)/admin/mail/sent/page.tsx | 1007 +++++++++-------- .../app/(main)/admin/mail/templates/page.tsx | 6 +- frontend/app/(main)/admin/mail/trash/page.tsx | 8 +- frontend/lib/api/mail.ts | 5 +- 35 files changed, 1050 insertions(+), 748 deletions(-) create mode 100644 backend-node/data/mail-sent/1bb5ebfe-3f6c-4884-a043-161ae3f74f75.json create mode 100644 backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json create mode 100644 backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json create mode 100644 backend-node/data/mail-sent/4a32bab5-364e-4037-bb00-31d2905824db.json create mode 100644 backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json create mode 100644 backend-node/data/mail-sent/cf892a77-1998-4165-bb9d-b390451465b2.json diff --git a/backend-node/data/mail-sent/1bb5ebfe-3f6c-4884-a043-161ae3f74f75.json b/backend-node/data/mail-sent/1bb5ebfe-3f6c-4884-a043-161ae3f74f75.json new file mode 100644 index 00000000..2f624e9c --- /dev/null +++ b/backend-node/data/mail-sent/1bb5ebfe-3f6c-4884-a043-161ae3f74f75.json @@ -0,0 +1,16 @@ +{ + "id": "1bb5ebfe-3f6c-4884-a043-161ae3f74f75", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [], + "cc": [], + "bcc": [], + "subject": "Fwd: ㄴㅇㄹㅇㄴㄴㄹ 테스트트트", + "htmlContent": "\n\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n전달된 메일:\n\n보낸사람: \"이희진\" \n날짜: 2025. 10. 22. 오후 4:24:54\n제목: ㄴㅇㄹㅇㄴㄴㄹ\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nㄹㅇㄴㄹㅇㄴㄹㅇㄴ\n", + "sentAt": "2025-10-22T07:49:50.811Z", + "status": "draft", + "isDraft": true, + "updatedAt": "2025-10-22T07:49:50.811Z", + "deletedAt": "2025-10-22T07:50:14.211Z" +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json b/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json new file mode 100644 index 00000000..683ad20c --- /dev/null +++ b/backend-node/data/mail-sent/1d997eeb-3d61-427d-8b54-119d4372b9b3.json @@ -0,0 +1,18 @@ +{ + "id": "1d997eeb-3d61-427d-8b54-119d4372b9b3", + "sentAt": "2025-10-22T07:13:30.905Z", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [ + "zian9227@naver.com" + ], + "subject": "Fwd: ㄴ", + "htmlContent": "\r\n
\r\n

전달히야야양


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
전달된 메일:

보낸사람: \"이희진\"
날짜: 2025. 10. 22. 오후 12:58:15
제목: ㄴ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ㄴㅇㄹㄴㅇㄹㄴㅇㄹ

\r\n
\r\n ", + "status": "success", + "messageId": "", + "accepted": [ + "zian9227@naver.com" + ], + "rejected": [] +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json b/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json new file mode 100644 index 00000000..5090fdd2 --- /dev/null +++ b/backend-node/data/mail-sent/331d95d6-3a13-4657-bc75-ab0811712eb8.json @@ -0,0 +1,18 @@ +{ + "id": "331d95d6-3a13-4657-bc75-ab0811712eb8", + "sentAt": "2025-10-22T07:18:18.240Z", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [ + "zian9227@naver.com" + ], + "subject": "ㅁㄴㅇㄹㅁㄴㅇㄹ", + "htmlContent": "\r\n
\r\n

ㅁㄴㅇㄹㅁㄴㅇㄹㄴㅇㄹㄴㅇㄹ

\r\n
\r\n ", + "status": "success", + "messageId": "", + "accepted": [ + "zian9227@naver.com" + ], + "rejected": [] +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/375f2326-ca86-468a-bfc3-2d4c3825577b.json b/backend-node/data/mail-sent/375f2326-ca86-468a-bfc3-2d4c3825577b.json index dc1a0eb1..c142808d 100644 --- a/backend-node/data/mail-sent/375f2326-ca86-468a-bfc3-2d4c3825577b.json +++ b/backend-node/data/mail-sent/375f2326-ca86-468a-bfc3-2d4c3825577b.json @@ -14,5 +14,6 @@ "accepted": [ "zian9227@naver.com" ], - "rejected": [] + "rejected": [], + "deletedAt": "2025-10-22T07:11:04.666Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/386e334a-df76-440c-ae8a-9bf06982fdc8.json b/backend-node/data/mail-sent/386e334a-df76-440c-ae8a-9bf06982fdc8.json index 5191bb6a..31da5552 100644 --- a/backend-node/data/mail-sent/386e334a-df76-440c-ae8a-9bf06982fdc8.json +++ b/backend-node/data/mail-sent/386e334a-df76-440c-ae8a-9bf06982fdc8.json @@ -11,5 +11,6 @@ "sentAt": "2025-10-22T07:04:27.192Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T07:04:57.280Z" + "updatedAt": "2025-10-22T07:04:57.280Z", + "deletedAt": "2025-10-22T07:50:17.136Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/3d411dc4-69a6-4236-b878-9693dff881be.json b/backend-node/data/mail-sent/3d411dc4-69a6-4236-b878-9693dff881be.json index e5936003..aa107de7 100644 --- a/backend-node/data/mail-sent/3d411dc4-69a6-4236-b878-9693dff881be.json +++ b/backend-node/data/mail-sent/3d411dc4-69a6-4236-b878-9693dff881be.json @@ -13,5 +13,6 @@ "sentAt": "2025-10-22T06:56:51.060Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:56:51.060Z" + "updatedAt": "2025-10-22T06:56:51.060Z", + "deletedAt": "2025-10-22T07:50:22.989Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/3e30a264-8431-44c7-96ef-eed551e66a11.json b/backend-node/data/mail-sent/3e30a264-8431-44c7-96ef-eed551e66a11.json index a5809da2..d824d67b 100644 --- a/backend-node/data/mail-sent/3e30a264-8431-44c7-96ef-eed551e66a11.json +++ b/backend-node/data/mail-sent/3e30a264-8431-44c7-96ef-eed551e66a11.json @@ -11,5 +11,6 @@ "sentAt": "2025-10-22T06:57:53.335Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T07:00:23.394Z" + "updatedAt": "2025-10-22T07:00:23.394Z", + "deletedAt": "2025-10-22T07:50:20.510Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/4a32bab5-364e-4037-bb00-31d2905824db.json b/backend-node/data/mail-sent/4a32bab5-364e-4037-bb00-31d2905824db.json new file mode 100644 index 00000000..92de4a0c --- /dev/null +++ b/backend-node/data/mail-sent/4a32bab5-364e-4037-bb00-31d2905824db.json @@ -0,0 +1,16 @@ +{ + "id": "4a32bab5-364e-4037-bb00-31d2905824db", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [], + "cc": [], + "bcc": [], + "subject": "테스트 마지가", + "htmlContent": "ㅁㄴㅇㄹ", + "sentAt": "2025-10-22T07:49:29.948Z", + "status": "draft", + "isDraft": true, + "updatedAt": "2025-10-22T07:49:29.948Z", + "deletedAt": "2025-10-22T07:50:12.374Z" +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/5bfb2acd-023a-4865-a738-2900179db5fb.json b/backend-node/data/mail-sent/5bfb2acd-023a-4865-a738-2900179db5fb.json index aecbe48e..5f5a5cfc 100644 --- a/backend-node/data/mail-sent/5bfb2acd-023a-4865-a738-2900179db5fb.json +++ b/backend-node/data/mail-sent/5bfb2acd-023a-4865-a738-2900179db5fb.json @@ -11,5 +11,6 @@ "sentAt": "2025-10-22T07:03:09.080Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T07:03:39.150Z" + "updatedAt": "2025-10-22T07:03:39.150Z", + "deletedAt": "2025-10-22T07:50:19.035Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/683c1323-1895-403a-bb9a-4e111a8909f6.json b/backend-node/data/mail-sent/683c1323-1895-403a-bb9a-4e111a8909f6.json index 6485eb1b..b3c3259f 100644 --- a/backend-node/data/mail-sent/683c1323-1895-403a-bb9a-4e111a8909f6.json +++ b/backend-node/data/mail-sent/683c1323-1895-403a-bb9a-4e111a8909f6.json @@ -13,5 +13,6 @@ "sentAt": "2025-10-22T06:54:55.097Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:54:55.097Z" + "updatedAt": "2025-10-22T06:54:55.097Z", + "deletedAt": "2025-10-22T07:50:24.672Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/7bed27d5-dae4-4ba8-85d0-c474c4fb907a.json b/backend-node/data/mail-sent/7bed27d5-dae4-4ba8-85d0-c474c4fb907a.json index 438aad96..d9edbdeb 100644 --- a/backend-node/data/mail-sent/7bed27d5-dae4-4ba8-85d0-c474c4fb907a.json +++ b/backend-node/data/mail-sent/7bed27d5-dae4-4ba8-85d0-c474c4fb907a.json @@ -11,5 +11,6 @@ "sentAt": "2025-10-22T06:41:52.984Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:46:23.051Z" + "updatedAt": "2025-10-22T06:46:23.051Z", + "deletedAt": "2025-10-22T07:50:29.124Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/8990ea86-3112-4e7c-b3e0-8b494181c4e0.json b/backend-node/data/mail-sent/8990ea86-3112-4e7c-b3e0-8b494181c4e0.json index e3150d8f..f0ed2dcf 100644 --- a/backend-node/data/mail-sent/8990ea86-3112-4e7c-b3e0-8b494181c4e0.json +++ b/backend-node/data/mail-sent/8990ea86-3112-4e7c-b3e0-8b494181c4e0.json @@ -8,5 +8,6 @@ "sentAt": "2025-10-22T06:17:31.379Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:17:31.379Z" + "updatedAt": "2025-10-22T06:17:31.379Z", + "deletedAt": "2025-10-22T07:50:30.736Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/9ab1e5ee-4f5e-4b79-9769-5e2a1e1ffc8e.json b/backend-node/data/mail-sent/9ab1e5ee-4f5e-4b79-9769-5e2a1e1ffc8e.json index 831d8414..31bde67a 100644 --- a/backend-node/data/mail-sent/9ab1e5ee-4f5e-4b79-9769-5e2a1e1ffc8e.json +++ b/backend-node/data/mail-sent/9ab1e5ee-4f5e-4b79-9769-5e2a1e1ffc8e.json @@ -14,5 +14,6 @@ "accepted": [ "zian9227@naver.com" ], - "rejected": [] + "rejected": [], + "deletedAt": "2025-10-22T07:11:10.245Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/9d0b9fcf-cabf-4053-b6b6-6e110add22de.json b/backend-node/data/mail-sent/9d0b9fcf-cabf-4053-b6b6-6e110add22de.json index ce2d258c..2ace7d67 100644 --- a/backend-node/data/mail-sent/9d0b9fcf-cabf-4053-b6b6-6e110add22de.json +++ b/backend-node/data/mail-sent/9d0b9fcf-cabf-4053-b6b6-6e110add22de.json @@ -13,5 +13,6 @@ "sentAt": "2025-10-22T06:50:04.224Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:50:04.224Z" + "updatedAt": "2025-10-22T06:50:04.224Z", + "deletedAt": "2025-10-22T07:50:26.224Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json b/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json new file mode 100644 index 00000000..5cf165c3 --- /dev/null +++ b/backend-node/data/mail-sent/a638f7d0-ee31-47fa-9f72-de66ef31ea44.json @@ -0,0 +1,18 @@ +{ + "id": "a638f7d0-ee31-47fa-9f72-de66ef31ea44", + "sentAt": "2025-10-22T07:21:13.723Z", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [ + "zian9227@naver.com" + ], + "subject": "ㄹㅇㄴㅁㄹㅇㄴㅁ", + "htmlContent": "\r\n
\r\n

ㄹㅇㄴㅁㄹㅇㄴㅁㅇㄹㅇㄴㅁ

\r\n
\r\n ", + "status": "success", + "messageId": "<5ea07d02-78bf-a655-8289-bcbd8eaf7741@wace.me>", + "accepted": [ + "zian9227@naver.com" + ], + "rejected": [] +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/b293e530-2b2d-4b8a-8081-d103fab5a13f.json b/backend-node/data/mail-sent/b293e530-2b2d-4b8a-8081-d103fab5a13f.json index dfc37164..77d9053f 100644 --- a/backend-node/data/mail-sent/b293e530-2b2d-4b8a-8081-d103fab5a13f.json +++ b/backend-node/data/mail-sent/b293e530-2b2d-4b8a-8081-d103fab5a13f.json @@ -13,5 +13,6 @@ "sentAt": "2025-10-22T06:47:53.815Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:48:53.876Z" + "updatedAt": "2025-10-22T06:48:53.876Z", + "deletedAt": "2025-10-22T07:50:27.706Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/cf892a77-1998-4165-bb9d-b390451465b2.json b/backend-node/data/mail-sent/cf892a77-1998-4165-bb9d-b390451465b2.json new file mode 100644 index 00000000..426f81fb --- /dev/null +++ b/backend-node/data/mail-sent/cf892a77-1998-4165-bb9d-b390451465b2.json @@ -0,0 +1,16 @@ +{ + "id": "cf892a77-1998-4165-bb9d-b390451465b2", + "accountId": "account-1759310844272", + "accountName": "이희진", + "accountEmail": "hjlee@wace.me", + "to": [], + "cc": [], + "bcc": [], + "subject": "Fwd: ㄴ", + "htmlContent": "\n\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n전달된 메일:\n\n보낸사람: \"이희진\" \n날짜: 2025. 10. 22. 오후 12:58:15\n제목: ㄴ\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nㄴㅇㄹㄴㅇㄹㄴㅇㄹ\n", + "sentAt": "2025-10-22T07:06:11.620Z", + "status": "draft", + "isDraft": true, + "updatedAt": "2025-10-22T07:07:11.749Z", + "deletedAt": "2025-10-22T07:50:15.739Z" +} \ No newline at end of file diff --git a/backend-node/data/mail-sent/e3501abc-cd31-4b20-bb02-3c7ddbe54eb8.json b/backend-node/data/mail-sent/e3501abc-cd31-4b20-bb02-3c7ddbe54eb8.json index 9c4ac60e..cf31f7dc 100644 --- a/backend-node/data/mail-sent/e3501abc-cd31-4b20-bb02-3c7ddbe54eb8.json +++ b/backend-node/data/mail-sent/e3501abc-cd31-4b20-bb02-3c7ddbe54eb8.json @@ -8,5 +8,6 @@ "sentAt": "2025-10-22T06:15:02.128Z", "status": "draft", "isDraft": true, - "updatedAt": "2025-10-22T06:15:02.128Z" + "updatedAt": "2025-10-22T06:15:02.128Z", + "deletedAt": "2025-10-22T07:08:43.543Z" } \ No newline at end of file diff --git a/backend-node/data/mail-sent/fd2a8b41-2e6e-4e5e-b8e8-63d31efc5082.json b/backend-node/data/mail-sent/fd2a8b41-2e6e-4e5e-b8e8-63d31efc5082.json index e9f68940..073c20f0 100644 --- a/backend-node/data/mail-sent/fd2a8b41-2e6e-4e5e-b8e8-63d31efc5082.json +++ b/backend-node/data/mail-sent/fd2a8b41-2e6e-4e5e-b8e8-63d31efc5082.json @@ -23,5 +23,6 @@ "accepted": [ "zian9227@naver.com" ], - "rejected": [] + "rejected": [], + "deletedAt": "2025-10-22T07:11:12.907Z" } \ No newline at end of file diff --git a/backend-node/src/controllers/mailReceiveBasicController.ts b/backend-node/src/controllers/mailReceiveBasicController.ts index d80cff7e..2de79185 100644 --- a/backend-node/src/controllers/mailReceiveBasicController.ts +++ b/backend-node/src/controllers/mailReceiveBasicController.ts @@ -18,11 +18,11 @@ export class MailReceiveBasicController { */ async getMailList(req: Request, res: Response) { try { - console.log('📬 메일 목록 조회 요청:', { - params: req.params, - path: req.path, - originalUrl: req.originalUrl - }); + // console.log('📬 메일 목록 조회 요청:', { + // params: req.params, + // path: req.path, + // originalUrl: req.originalUrl + // }); const { accountId } = req.params; const limit = parseInt(req.query.limit as string) || 50; @@ -49,11 +49,11 @@ export class MailReceiveBasicController { */ async getMailDetail(req: Request, res: Response) { try { - console.log('🔍 메일 상세 조회 요청:', { - params: req.params, - path: req.path, - originalUrl: req.originalUrl - }); + // console.log('🔍 메일 상세 조회 요청:', { + // params: req.params, + // path: req.path, + // originalUrl: req.originalUrl + // }); const { accountId, seqno } = req.params; const seqnoNumber = parseInt(seqno, 10); @@ -121,39 +121,39 @@ export class MailReceiveBasicController { */ async downloadAttachment(req: Request, res: Response) { try { - console.log('📎🎯 컨트롤러 downloadAttachment 진입'); + // console.log('📎🎯 컨트롤러 downloadAttachment 진입'); const { accountId, seqno, index } = req.params; - console.log(`📎 파라미터: accountId=${accountId}, seqno=${seqno}, index=${index}`); + // console.log(`📎 파라미터: accountId=${accountId}, seqno=${seqno}, index=${index}`); const seqnoNumber = parseInt(seqno, 10); const indexNumber = parseInt(index, 10); if (isNaN(seqnoNumber) || isNaN(indexNumber)) { - console.log('❌ 유효하지 않은 파라미터'); + // console.log('❌ 유효하지 않은 파라미터'); return res.status(400).json({ success: false, message: '유효하지 않은 파라미터입니다.', }); } - console.log('📎 서비스 호출 시작...'); + // console.log('📎 서비스 호출 시작...'); const result = await this.mailReceiveService.downloadAttachment( accountId, seqnoNumber, indexNumber ); - console.log(`📎 서비스 호출 완료: result=${result ? '있음' : '없음'}`); + // console.log(`📎 서비스 호출 완료: result=${result ? '있음' : '없음'}`); if (!result) { - console.log('❌ 첨부파일을 찾을 수 없음'); + // console.log('❌ 첨부파일을 찾을 수 없음'); return res.status(404).json({ success: false, message: '첨부파일을 찾을 수 없습니다.', }); } - console.log(`📎 파일 다운로드 시작: ${result.filename}`); - console.log(`📎 파일 경로: ${result.filePath}`); + // console.log(`📎 파일 다운로드 시작: ${result.filename}`); + // console.log(`📎 파일 경로: ${result.filePath}`); // 파일 다운로드 res.download(result.filePath, result.filename, (err) => { @@ -247,3 +247,5 @@ export class MailReceiveBasicController { } } + +export const mailReceiveBasicController = new MailReceiveBasicController(); diff --git a/backend-node/src/controllers/mailSendSimpleController.ts b/backend-node/src/controllers/mailSendSimpleController.ts index a29be016..4736e98c 100644 --- a/backend-node/src/controllers/mailSendSimpleController.ts +++ b/backend-node/src/controllers/mailSendSimpleController.ts @@ -7,14 +7,14 @@ export class MailSendSimpleController { */ async sendMail(req: Request, res: Response) { try { - 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, - }); + // 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, + // }); // FormData에서 JSON 문자열 파싱 const accountId = req.body.accountId; @@ -31,7 +31,7 @@ export class MailSendSimpleController { // 필수 파라미터 검증 if (!accountId || !to || !Array.isArray(to) || to.length === 0) { - console.log('❌ 필수 파라미터 누락'); + // console.log('❌ 필수 파라미터 누락'); return res.status(400).json({ success: false, message: '계정 ID와 수신자 이메일이 필요합니다.', @@ -63,9 +63,9 @@ export class MailSendSimpleController { if (req.body.fileNames) { try { parsedFileNames = JSON.parse(req.body.fileNames); - console.log('📎 프론트엔드에서 받은 파일명들:', parsedFileNames); + // console.log('📎 프론트엔드에서 받은 파일명들:', parsedFileNames); } catch (e) { - console.warn('파일명 파싱 실패, multer originalname 사용'); + // console.warn('파일명 파싱 실패, multer originalname 사용'); } } @@ -83,10 +83,10 @@ export class MailSendSimpleController { }); }); - console.log('📎 최종 첨부파일 정보:', attachments.map(a => ({ - filename: a.filename, - path: a.path.split('/').pop() - }))); + // console.log('📎 최종 첨부파일 정보:', attachments.map(a => ({ + // filename: a.filename, + // path: a.path.split('/').pop() + // }))); } // 메일 발송 @@ -130,16 +130,24 @@ export class MailSendSimpleController { */ async sendBulkMail(req: Request, res: Response) { try { - const { accountId, templateId, subject, recipients } = req.body; + const { accountId, templateId, customHtml, subject, recipients } = req.body; // 필수 파라미터 검증 - if (!accountId || !templateId || !subject || !recipients || !Array.isArray(recipients)) { + if (!accountId || !subject || !recipients || !Array.isArray(recipients)) { return res.status(400).json({ success: false, message: '필수 파라미터가 누락되었습니다.', }); } + // 템플릿 또는 직접 작성 중 하나는 있어야 함 + if (!templateId && !customHtml) { + return res.status(400).json({ + success: false, + message: '템플릿 또는 메일 내용 중 하나는 필수입니다.', + }); + } + if (recipients.length === 0) { return res.status(400).json({ success: false, @@ -147,12 +155,13 @@ export class MailSendSimpleController { }); } - console.log(`📧 대량 발송 요청: ${recipients.length}명`); + // console.log(`📧 대량 발송 요청: ${recipients.length}명`); // 대량 발송 실행 const result = await mailSendSimpleService.sendBulkMail({ accountId, - templateId, + templateId, // 선택 + customHtml, // 선택 subject, recipients, }); diff --git a/backend-node/src/services/mailReceiveBasicService.ts b/backend-node/src/services/mailReceiveBasicService.ts index e3f2e278..6bec4d93 100644 --- a/backend-node/src/services/mailReceiveBasicService.ts +++ b/backend-node/src/services/mailReceiveBasicService.ts @@ -119,7 +119,7 @@ export class MailReceiveBasicService { tls: true, }; - // console.log(`📧 IMAP 연결 시도 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, 이메일: ${imapConfig.user}`); + // // console.log(`📧 IMAP 연결 시도 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, 이메일: ${imapConfig.user}`); return new Promise((resolve, reject) => { const imap = this.createImapConnection(imapConfig); @@ -133,7 +133,7 @@ export class MailReceiveBasicService { }, 30000); imap.once("ready", () => { - // console.log('✅ IMAP 연결 성공! INBOX 열기 시도...'); + // // console.log('✅ IMAP 연결 성공! INBOX 열기 시도...'); clearTimeout(timeout); imap.openBox("INBOX", true, (err: any, box: any) => { @@ -143,10 +143,10 @@ export class MailReceiveBasicService { return reject(err); } - // console.log(`📬 INBOX 열림 - 전체 메일 수: ${box.messages.total}`); + // // console.log(`📬 INBOX 열림 - 전체 메일 수: ${box.messages.total}`); const totalMessages = box.messages.total; if (totalMessages === 0) { - // console.log('📭 메일함이 비어있습니다'); + // // console.log('📭 메일함이 비어있습니다'); imap.end(); return resolve([]); } @@ -155,19 +155,19 @@ export class MailReceiveBasicService { const start = Math.max(1, totalMessages - limit + 1); const end = totalMessages; - // console.log(`📨 메일 가져오기 시작 - 범위: ${start}~${end}`); + // // console.log(`📨 메일 가져오기 시작 - 범위: ${start}~${end}`); const fetch = imap.seq.fetch(`${start}:${end}`, { bodies: ["HEADER", "TEXT"], struct: true, }); - // console.log(`📦 fetch 객체 생성 완료`); + // // console.log(`📦 fetch 객체 생성 완료`); let processedCount = 0; const totalToProcess = end - start + 1; fetch.on("message", (msg: any, seqno: any) => { - // console.log(`📬 메일 #${seqno} 처리 시작`); + // // console.log(`📬 메일 #${seqno} 처리 시작`); let header: string = ""; let body: string = ""; let attributes: any = null; @@ -225,7 +225,7 @@ export class MailReceiveBasicService { }; mails.push(mail); - // console.log(`✓ 메일 #${seqno} 파싱 완료 (${mails.length}/${totalToProcess})`); + // // console.log(`✓ 메일 #${seqno} 파싱 완료 (${mails.length}/${totalToProcess})`); processedCount++; } catch (parseError) { // console.error(`메일 #${seqno} 파싱 오류:`, parseError); @@ -243,18 +243,18 @@ export class MailReceiveBasicService { }); fetch.once("end", () => { - // console.log(`📭 fetch 종료 - 처리 완료 대기 중... (현재: ${mails.length}개)`); + // // console.log(`📭 fetch 종료 - 처리 완료 대기 중... (현재: ${mails.length}개)`); // 모든 메일 처리가 완료될 때까지 대기 const checkComplete = setInterval(() => { - // console.log(`⏳ 대기 중 - 처리됨: ${processedCount}/${totalToProcess}, 메일: ${mails.length}개`); + // // console.log(`⏳ 대기 중 - 처리됨: ${processedCount}/${totalToProcess}, 메일: ${mails.length}개`); if (processedCount >= totalToProcess) { clearInterval(checkComplete); - // console.log(`✅ 메일 가져오기 완료 - 총 ${mails.length}개`); + // // console.log(`✅ 메일 가져오기 완료 - 총 ${mails.length}개`); imap.end(); // 최신 메일이 위로 오도록 정렬 mails.sort((a, b) => b.date.getTime() - a.date.getTime()); - // console.log(`📤 메일 목록 반환: ${mails.length}개`); + // // console.log(`📤 메일 목록 반환: ${mails.length}개`); resolve(mails); } }, 100); @@ -262,7 +262,7 @@ export class MailReceiveBasicService { // 최대 10초 대기 setTimeout(() => { clearInterval(checkComplete); - // console.log(`⚠️ 타임아웃 - 부분 반환: ${mails.length}/${totalToProcess}개`); + // // console.log(`⚠️ 타임아웃 - 부분 반환: ${mails.length}/${totalToProcess}개`); imap.end(); mails.sort((a, b) => b.date.getTime() - a.date.getTime()); resolve(mails); @@ -278,10 +278,10 @@ export class MailReceiveBasicService { }); imap.once("end", () => { - // console.log('🔌 IMAP 연결 종료'); + // // console.log('🔌 IMAP 연결 종료'); }); - // console.log('🔗 IMAP.connect() 호출...'); + // // console.log('🔗 IMAP.connect() 호출...'); imap.connect(); }); } @@ -332,9 +332,9 @@ export class MailReceiveBasicService { return reject(err); } - console.log( - `📬 INBOX 정보 - 전체 메일: ${box.messages.total}, 요청한 seqno: ${seqno}` - ); + // console.log( + // `📬 INBOX 정보 - 전체 메일: ${box.messages.total}, 요청한 seqno: ${seqno}` + // ); if (seqno > box.messages.total || seqno < 1) { console.error( @@ -353,21 +353,21 @@ export class MailReceiveBasicService { let parsingComplete = false; fetch.on("message", (msg: any, seqnum: any) => { - console.log(`📨 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`); + // console.log(`📨 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`); msg.on("body", (stream: any, info: any) => { - console.log(`📝 메일 본문 스트림 시작 - which: ${info.which}`); + // console.log(`📝 메일 본문 스트림 시작 - which: ${info.which}`); let buffer = ""; stream.on("data", (chunk: any) => { buffer += chunk.toString("utf8"); }); stream.once("end", async () => { - console.log( - `✅ 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}` - ); + // console.log( + // `✅ 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}` + // ); try { const parsed = await simpleParser(buffer); - console.log(`✅ 메일 파싱 완료 - 제목: ${parsed.subject}`); + // console.log(`✅ 메일 파싱 완료 - 제목: ${parsed.subject}`); const fromAddress = Array.isArray(parsed.from) ? parsed.from[0] @@ -415,7 +415,7 @@ export class MailReceiveBasicService { // msg 전체가 처리되었을 때 이벤트 msg.once("end", () => { - console.log(`📮 메일 메시지 처리 완료 - seqnum: ${seqnum}`); + // console.log(`📮 메일 메시지 처리 완료 - seqnum: ${seqnum}`); }); }); @@ -426,15 +426,15 @@ export class MailReceiveBasicService { }); fetch.once("end", () => { - console.log(`🏁 Fetch 종료 - parsingComplete: ${parsingComplete}`); + // console.log(`🏁 Fetch 종료 - parsingComplete: ${parsingComplete}`); // 비동기 파싱이 완료될 때까지 대기 const waitForParsing = setInterval(() => { if (parsingComplete) { clearInterval(waitForParsing); - console.log( - `✅ 파싱 완료 대기 종료 - mailDetail이 ${mailDetail ? "존재함" : "null"}` - ); + // console.log( + // `✅ 파싱 완료 대기 종료 - mailDetail이 ${mailDetail ? "존재함" : "null"}` + // ); imap.end(); resolve(mailDetail); } @@ -499,7 +499,7 @@ export class MailReceiveBasicService { imap.once("ready", () => { clearTimeout(timeout); - console.log(`🔗 IMAP 연결 성공 - 읽음 표시 시작 (seqno=${seqno})`); + // console.log(`🔗 IMAP 연결 성공 - 읽음 표시 시작 (seqno=${seqno})`); // false로 변경: 쓰기 가능 모드로 INBOX 열기 imap.openBox("INBOX", false, (err: any, box: any) => { @@ -509,7 +509,7 @@ export class MailReceiveBasicService { return reject(err); } - console.log(`📬 INBOX 열림 (쓰기 가능 모드)`); + // console.log(`📬 INBOX 열림 (쓰기 가능 모드)`); imap.seq.addFlags(seqno, ["\\Seen"], (flagErr: any) => { imap.end(); @@ -517,7 +517,7 @@ export class MailReceiveBasicService { console.error("❌ 읽음 플래그 설정 실패:", flagErr); reject(flagErr); } else { - console.log("✅ 읽음 플래그 설정 성공 - seqno:", seqno); + // console.log("✅ 읽음 플래그 설정 성공 - seqno:", seqno); resolve({ success: true, message: "메일을 읽음으로 표시했습니다.", @@ -537,7 +537,7 @@ export class MailReceiveBasicService { clearTimeout(timeout); }); - console.log(`🔌 IMAP 연결 시도 중... (host=${imapConfig.host}, port=${imapConfig.port})`); + // console.log(`🔌 IMAP 연결 시도 중... (host=${imapConfig.host}, port=${imapConfig.port})`); imap.connect(); }); } @@ -556,7 +556,7 @@ export class MailReceiveBasicService { // 비밀번호 복호화 const decryptedPassword = encryptionService.decrypt(account.smtpPassword); - // console.log(`🔐 IMAP 테스트 - 이메일: ${account.email}, 비밀번호 길이: ${decryptedPassword.length}`); + // // console.log(`🔐 IMAP 테스트 - 이메일: ${account.email}, 비밀번호 길이: ${decryptedPassword.length}`); const accountAny = account as any; const imapConfig: ImapConfig = { @@ -566,7 +566,7 @@ export class MailReceiveBasicService { port: this.inferImapPort(account.smtpPort, accountAny.imapPort), tls: true, }; - // console.log(`📧 IMAP 설정 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, TLS: ${imapConfig.tls}`); + // // console.log(`📧 IMAP 설정 - 호스트: ${imapConfig.host}, 포트: ${imapConfig.port}, TLS: ${imapConfig.tls}`); return new Promise((resolve, reject) => { const imap = this.createImapConnection(imapConfig); @@ -692,32 +692,32 @@ export class MailReceiveBasicService { let parsingComplete = false; fetch.on("message", (msg: any, seqnum: any) => { - console.log(`📎 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`); + // console.log(`📎 메일 메시지 이벤트 발생 - seqnum: ${seqnum}`); msg.on("body", (stream: any, info: any) => { - console.log(`📎 메일 본문 스트림 시작`); + // console.log(`📎 메일 본문 스트림 시작`); let buffer = ""; stream.on("data", (chunk: any) => { buffer += chunk.toString("utf8"); }); stream.once("end", async () => { - console.log( - `📎 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}` - ); + // console.log( + // `📎 메일 본문 스트림 종료 - 버퍼 크기: ${buffer.length}` + // ); try { const parsed = await simpleParser(buffer); - console.log( - `📎 파싱 완료 - 첨부파일 개수: ${parsed.attachments?.length || 0}` - ); + // console.log( + // `📎 파싱 완료 - 첨부파일 개수: ${parsed.attachments?.length || 0}` + // ); if ( parsed.attachments && parsed.attachments[attachmentIndex] ) { const attachment = parsed.attachments[attachmentIndex]; - console.log( - `📎 첨부파일 발견 (index ${attachmentIndex}): ${attachment.filename}` - ); + // console.log( + // `📎 첨부파일 발견 (index ${attachmentIndex}): ${attachment.filename}` + // ); // 안전한 파일명 생성 const safeFilename = this.sanitizeFilename( @@ -729,7 +729,7 @@ export class MailReceiveBasicService { // 파일 저장 await fs.writeFile(filePath, attachment.content); - console.log(`📎 파일 저장 완료: ${filePath}`); + // console.log(`📎 파일 저장 완료: ${filePath}`); attachmentResult = { filePath, @@ -739,9 +739,9 @@ export class MailReceiveBasicService { }; parsingComplete = true; } else { - console.log( - `❌ 첨부파일 index ${attachmentIndex}를 찾을 수 없음 (총 ${parsed.attachments?.length || 0}개)` - ); + // console.log( + // `❌ 첨부파일 index ${attachmentIndex}를 찾을 수 없음 (총 ${parsed.attachments?.length || 0}개)` + // ); parsingComplete = true; } } catch (parseError) { @@ -759,14 +759,14 @@ export class MailReceiveBasicService { }); fetch.once("end", () => { - console.log('📎 fetch.once("end") 호출됨 - 파싱 완료 대기 시작...'); + // console.log('📎 fetch.once("end") 호출됨 - 파싱 완료 대기 시작...'); // 파싱 완료를 기다림 (최대 5초) const checkComplete = setInterval(() => { if (parsingComplete) { - console.log( - `✅ 파싱 완료 확인 - attachmentResult: ${attachmentResult ? "있음" : "없음"}` - ); + // console.log( + // `✅ 파싱 완료 확인 - attachmentResult: ${attachmentResult ? "있음" : "없음"}` + // ); clearInterval(checkComplete); imap.end(); resolve(attachmentResult); @@ -775,9 +775,9 @@ export class MailReceiveBasicService { setTimeout(() => { clearInterval(checkComplete); - console.log( - `⚠️ 타임아웃 - attachmentResult: ${attachmentResult ? "있음" : "없음"}` - ); + // console.log( + // `⚠️ 타임아웃 - attachmentResult: ${attachmentResult ? "있음" : "없음"}` + // ); imap.end(); resolve(attachmentResult); }, 5000); @@ -840,7 +840,7 @@ export class MailReceiveBasicService { imap.once("ready", () => { clearTimeout(timeout); - console.log(`🔗 IMAP 연결 성공 - 메일 삭제 시작 (seqno=${seqno})`); + // console.log(`🔗 IMAP 연결 성공 - 메일 삭제 시작 (seqno=${seqno})`); imap.openBox("INBOX", false, (err: any) => { if (err) { @@ -857,7 +857,7 @@ export class MailReceiveBasicService { return reject(flagErr); } - console.log(`✓ 삭제 플래그 추가 완료 (seqno=${seqno})`); + // console.log(`✓ 삭제 플래그 추가 완료 (seqno=${seqno})`); // 삭제 플래그가 표시된 메일을 영구 삭제 (실제로는 휴지통으로 이동) imap.expunge((expungeErr: any) => { @@ -868,7 +868,7 @@ export class MailReceiveBasicService { return reject(expungeErr); } - console.log(`🗑️ 메일 삭제 완료: seqno=${seqno}`); + // console.log(`🗑️ 메일 삭제 완료: seqno=${seqno}`); resolve({ success: true, message: "메일이 삭제되었습니다.", @@ -888,8 +888,10 @@ export class MailReceiveBasicService { clearTimeout(timeout); }); - console.log(`🔌 IMAP 연결 시도 중... (host=${config.host}, port=${config.port})`); + // console.log(`🔌 IMAP 연결 시도 중... (host=${config.host}, port=${config.port})`); imap.connect(); }); } } + +export const mailReceiveBasicService = new MailReceiveBasicService(); diff --git a/backend-node/src/services/mailSendSimpleService.ts b/backend-node/src/services/mailSendSimpleService.ts index a5de90ef..b4dce503 100644 --- a/backend-node/src/services/mailSendSimpleService.ts +++ b/backend-node/src/services/mailSendSimpleService.ts @@ -36,11 +36,12 @@ export interface SendMailResult { export interface BulkSendRequest { accountId: string; - templateId: string; + templateId?: string; // 템플릿 ID (선택) subject: string; + customHtml?: string; // 직접 작성한 HTML (선택) recipients: Array<{ email: string; - variables: Record; + variables?: Record; // 템플릿 사용 시에만 필요 }>; } @@ -85,7 +86,7 @@ class MailSendSimpleService { // 🎯 수정된 컴포넌트가 있으면 덮어쓰기 if (request.modifiedTemplateComponents && request.modifiedTemplateComponents.length > 0) { - console.log('✏️ 수정된 템플릿 컴포넌트 사용:', request.modifiedTemplateComponents.length); + // console.log('✏️ 수정된 템플릿 컴포넌트 사용:', request.modifiedTemplateComponents.length); template.components = request.modifiedTemplateComponents; } @@ -106,15 +107,15 @@ class MailSendSimpleService { // 4. 비밀번호 복호화 const decryptedPassword = encryptionService.decrypt(account.smtpPassword); - // console.log('🔐 비밀번호 복호화 완료'); - // console.log('🔐 암호화된 비밀번호 (일부):', account.smtpPassword.substring(0, 30) + '...'); - // console.log('🔐 복호화된 비밀번호 길이:', decryptedPassword.length); + // // console.log('🔐 비밀번호 복호화 완료'); + // // console.log('🔐 암호화된 비밀번호 (일부):', account.smtpPassword.substring(0, 30) + '...'); + // // console.log('🔐 복호화된 비밀번호 길이:', decryptedPassword.length); // 5. SMTP 연결 생성 // 포트 465는 SSL/TLS를 사용해야 함 const isSecure = account.smtpPort === 465 ? true : (account.smtpSecure || false); - // console.log('📧 SMTP 연결 설정:', { + // // console.log('📧 SMTP 연결 설정:', { // host: account.smtpHost, // port: account.smtpPort, // secure: isSecure, @@ -134,7 +135,7 @@ class MailSendSimpleService { greetingTimeout: 30000, }); - console.log('📧 메일 발송 시도 중...'); + // console.log('📧 메일 발송 시도 중...'); // 6. 메일 발송 (CC, BCC, 첨부파일 지원) const mailOptions: any = { @@ -147,13 +148,13 @@ class MailSendSimpleService { // 참조(CC) 추가 if (request.cc && request.cc.length > 0) { mailOptions.cc = request.cc.join(', '); - // console.log('📧 참조(CC):', request.cc); + // // console.log('📧 참조(CC):', request.cc); } // 숨은참조(BCC) 추가 if (request.bcc && request.bcc.length > 0) { mailOptions.bcc = request.bcc.join(', '); - // console.log('🔒 숨은참조(BCC):', request.bcc); + // // console.log('🔒 숨은참조(BCC):', request.bcc); } // 첨부파일 추가 (한글 파일명 인코딩 처리) @@ -185,17 +186,17 @@ class MailSendSimpleService { } }; }); - console.log('📎 첨부파일 (원본):', request.attachments.map((a: any) => a.filename.replace(/^\d+-\d+_/, ''))); - console.log('📎 첨부파일 (인코딩):', mailOptions.attachments.map((a: any) => a.filename)); + // console.log('📎 첨부파일 (원본):', request.attachments.map((a: any) => a.filename.replace(/^\d+-\d+_/, ''))); + // console.log('📎 첨부파일 (인코딩):', mailOptions.attachments.map((a: any) => a.filename)); } const info = await transporter.sendMail(mailOptions); - console.log('✅ 메일 발송 성공:', { - messageId: info.messageId, - accepted: info.accepted, - rejected: info.rejected, - }); + // console.log('✅ 메일 발송 성공:', { + // messageId: info.messageId, + // accepted: info.accepted, + // rejected: info.rejected, + // }); // 발송 이력 저장 (성공) try { @@ -438,17 +439,18 @@ class MailSendSimpleService { let successCount = 0; let failedCount = 0; - console.log(`📧 대량 발송 시작: ${request.recipients.length}명`); + // console.log(`📧 대량 발송 시작: ${request.recipients.length}명`); // 순차 발송 (너무 빠르면 스팸으로 분류될 수 있음) for (const recipient of request.recipients) { try { const result = await this.sendMail({ accountId: request.accountId, - templateId: request.templateId, + templateId: request.templateId, // 템플릿이 있으면 사용 + customHtml: request.customHtml, // 직접 작성한 HTML이 있으면 사용 to: [recipient.email], subject: request.subject, - variables: recipient.variables, + variables: recipient.variables || {}, // 템플릿 사용 시에만 필요 }); if (result.success) { @@ -480,7 +482,7 @@ class MailSendSimpleService { await new Promise((resolve) => setTimeout(resolve, 500)); } - console.log(`✅ 대량 발송 완료: 성공 ${successCount}, 실패 ${failedCount}`); + // console.log(`✅ 대량 발송 완료: 성공 ${successCount}, 실패 ${failedCount}`); return { total: request.recipients.length, @@ -502,13 +504,13 @@ class MailSendSimpleService { // 비밀번호 복호화 const decryptedPassword = encryptionService.decrypt(account.smtpPassword); - // console.log('🔐 테스트용 비밀번호 복호화 완료'); - // console.log('🔐 복호화된 비밀번호 길이:', decryptedPassword.length); + // // console.log('🔐 테스트용 비밀번호 복호화 완료'); + // // console.log('🔐 복호화된 비밀번호 길이:', decryptedPassword.length); // 포트 465는 SSL/TLS를 사용해야 함 const isSecure = account.smtpPort === 465 ? true : (account.smtpSecure || false); - // console.log('🧪 SMTP 연결 테스트 시작:', { + // // console.log('🧪 SMTP 연결 테스트 시작:', { // host: account.smtpHost, // port: account.smtpPort, // secure: isSecure, @@ -531,7 +533,7 @@ class MailSendSimpleService { // 연결 테스트 await transporter.verify(); - console.log('✅ SMTP 연결 테스트 성공'); + // console.log('✅ SMTP 연결 테스트 성공'); return { success: true, message: 'SMTP 연결이 성공했습니다.' }; } catch (error) { const err = error as Error; diff --git a/backend-node/src/services/mailSentHistoryService.ts b/backend-node/src/services/mailSentHistoryService.ts index 884c4d02..f0a80265 100644 --- a/backend-node/src/services/mailSentHistoryService.ts +++ b/backend-node/src/services/mailSentHistoryService.ts @@ -53,7 +53,7 @@ class MailSentHistoryService { mode: 0o644, }); - console.log("발송 이력 저장:", history.id); + // console.log("발송 이력 저장:", history.id); } catch (error) { console.error("발송 이력 저장 실패:", error); // 파일 저장 실패해도 history 객체는 반환 (메일 발송은 성공했으므로) @@ -86,7 +86,7 @@ class MailSentHistoryService { try { // 디렉토리가 없으면 빈 배열 반환 if (!fs.existsSync(SENT_MAIL_DIR)) { - console.warn("메일 발송 이력 디렉토리가 없습니다:", SENT_MAIL_DIR); + // console.warn("메일 발송 이력 디렉토리가 없습니다:", SENT_MAIL_DIR); return { items: [], total: 0, @@ -221,7 +221,7 @@ class MailSentHistoryService { async saveDraft( data: Partial & { accountId: string } ): Promise { - console.log("📥 백엔드에서 받은 임시 저장 데이터:", data); + // console.log("📥 백엔드에서 받은 임시 저장 데이터:", data); const now = new Date().toISOString(); const draft: SentMailHistory = { @@ -243,7 +243,7 @@ class MailSentHistoryService { updatedAt: now, }; - console.log("💾 저장할 draft 객체:", draft); + // console.log("💾 저장할 draft 객체:", draft); try { if (!fs.existsSync(SENT_MAIL_DIR)) { @@ -256,7 +256,7 @@ class MailSentHistoryService { mode: 0o644, }); - console.log("💾 임시 저장:", draft.id); + // console.log("💾 임시 저장:", draft.id); } catch (error) { console.error("임시 저장 실패:", error); throw error; @@ -291,7 +291,7 @@ class MailSentHistoryService { mode: 0o644, }); - console.log("✏️ 임시 저장 업데이트:", id); + // console.log("✏️ 임시 저장 업데이트:", id); return updated; } catch (error) { console.error("임시 저장 업데이트 실패:", error); @@ -320,7 +320,7 @@ class MailSentHistoryService { mode: 0o644, }); - console.log("🗑️ 메일 삭제 (Soft Delete):", id); + // console.log("🗑️ 메일 삭제 (Soft Delete):", id); return true; } catch (error) { console.error("메일 삭제 실패:", error); @@ -349,7 +349,7 @@ class MailSentHistoryService { mode: 0o644, }); - console.log("♻️ 메일 복구:", id); + // console.log("♻️ 메일 복구:", id); return true; } catch (error) { console.error("메일 복구 실패:", error); @@ -369,7 +369,7 @@ class MailSentHistoryService { try { fs.unlinkSync(filePath); - console.log("🗑️ 메일 영구 삭제:", id); + // console.log("🗑️ 메일 영구 삭제:", id); return true; } catch (error) { console.error("메일 영구 삭제 실패:", error); @@ -406,7 +406,7 @@ class MailSentHistoryService { if (deletedDate < thirtyDaysAgo) { fs.unlinkSync(filePath); deletedCount++; - console.log("🗑️ 30일 지난 메일 자동 삭제:", mail.id); + // console.log("🗑️ 30일 지난 메일 자동 삭제:", mail.id); } } } catch (error) { diff --git a/backend-node/src/services/mailTemplateFileService.ts b/backend-node/src/services/mailTemplateFileService.ts index 3213dafd..adb72fff 100644 --- a/backend-node/src/services/mailTemplateFileService.ts +++ b/backend-node/src/services/mailTemplateFileService.ts @@ -173,7 +173,7 @@ class MailTemplateFileService { updatedAt: new Date().toISOString(), }; - // console.log(`📝 템플릿 저장 시도: ${id}, 크기: ${JSON.stringify(updated).length} bytes`); + // // console.log(`📝 템플릿 저장 시도: ${id}, 크기: ${JSON.stringify(updated).length} bytes`); await fs.writeFile( this.getTemplatePath(id), @@ -181,7 +181,7 @@ class MailTemplateFileService { "utf-8" ); - // console.log(`✅ 템플릿 저장 성공: ${id}`); + // // console.log(`✅ 템플릿 저장 성공: ${id}`); return updated; } catch (error) { // console.error(`❌ 템플릿 저장 실패: ${id}`, error); diff --git a/frontend/app/(main)/admin/mail/accounts/page.tsx b/frontend/app/(main)/admin/mail/accounts/page.tsx index 482452a8..3e716d25 100644 --- a/frontend/app/(main)/admin/mail/accounts/page.tsx +++ b/frontend/app/(main)/admin/mail/accounts/page.tsx @@ -38,11 +38,11 @@ export default function MailAccountsPage() { if (Array.isArray(data)) { setAccounts(data); } else { - console.error('API 응답이 배열이 아닙니다:', data); + // console.error('API 응답이 배열이 아닙니다:', data); setAccounts([]); } } catch (error) { - console.error('계정 로드 실패:', error); + // console.error('계정 로드 실패:', error); setAccounts([]); // 에러 시 빈 배열로 설정 // alert('계정 목록을 불러오는데 실패했습니다.'); } finally { @@ -93,7 +93,7 @@ export default function MailAccountsPage() { await loadAccounts(); alert('계정이 삭제되었습니다.'); } catch (error) { - console.error('계정 삭제 실패:', error); + // console.error('계정 삭제 실패:', error); alert('계정 삭제에 실패했습니다.'); } }; @@ -104,7 +104,7 @@ export default function MailAccountsPage() { await updateMailAccount(account.id, { status: newStatus }); await loadAccounts(); } catch (error) { - console.error('상태 변경 실패:', error); + // console.error('상태 변경 실패:', error); alert('상태 변경에 실패했습니다.'); } }; @@ -120,7 +120,7 @@ export default function MailAccountsPage() { alert(`❌ SMTP 연결 실패\n\n${result.message || '연결에 실패했습니다.'}`); } } catch (error: any) { - console.error('연결 테스트 실패:', error); + // console.error('연결 테스트 실패:', error); alert(`❌ SMTP 연결 테스트 실패\n\n${error.message || '알 수 없는 오류가 발생했습니다.'}`); } finally { setLoading(false); diff --git a/frontend/app/(main)/admin/mail/bulk-send/page.tsx b/frontend/app/(main)/admin/mail/bulk-send/page.tsx index bd5a00af..e6a85942 100644 --- a/frontend/app/(main)/admin/mail/bulk-send/page.tsx +++ b/frontend/app/(main)/admin/mail/bulk-send/page.tsx @@ -48,6 +48,8 @@ export default function BulkSendPage() { const [templates, setTemplates] = useState([]); const [selectedAccountId, setSelectedAccountId] = useState(""); const [selectedTemplateId, setSelectedTemplateId] = useState(""); + const [useTemplate, setUseTemplate] = useState(true); // 템플릿 사용 여부 + const [customHtml, setCustomHtml] = useState(""); // 직접 작성한 HTML const [subject, setSubject] = useState(""); const [recipients, setRecipients] = useState([]); const [csvFile, setCsvFile] = useState(null); @@ -63,7 +65,7 @@ export default function BulkSendPage() { const loadAccounts = async () => { try { const data = await getMailAccounts(); - setAccounts(data.filter((acc) => acc.isActive)); + setAccounts(data.filter((acc) => acc.status === 'active')); } catch (error: unknown) { const err = error as Error; toast({ @@ -164,7 +166,8 @@ export default function BulkSendPage() { return; } - if (!selectedTemplateId) { + // 템플릿 또는 직접 작성 중 하나는 있어야 함 + if (useTemplate && !selectedTemplateId) { toast({ title: "템플릿 선택 필요", description: "사용할 템플릿을 선택해주세요.", @@ -173,6 +176,15 @@ export default function BulkSendPage() { return; } + if (!useTemplate && !customHtml.trim()) { + toast({ + title: "내용 입력 필요", + description: "메일 내용을 입력해주세요.", + variant: "destructive", + }); + return; + } + if (!subject.trim()) { toast({ title: "제목 입력 필요", @@ -197,7 +209,8 @@ export default function BulkSendPage() { try { await sendBulkMail({ accountId: selectedAccountId, - templateId: selectedTemplateId, + templateId: useTemplate ? selectedTemplateId : undefined, + customHtml: !useTemplate ? customHtml : undefined, subject, recipients, onProgress: (sent, total) => { @@ -287,21 +300,51 @@ example2@example.com,김철수,XYZ회사`;
- - setUseTemplate(v === "template")}> + + - {templates.map((template) => ( - - {template.name} - - ))} + 템플릿 사용 + 직접 작성
+ {useTemplate ? ( +
+ + +
+ ) : ( +
+ +