From f054df2800858f98f2b6c642f1cdd3c8818639b6 Mon Sep 17 00:00:00 2001 From: fervovita Date: Wed, 15 Jul 2026 01:46:29 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Chore:=20=EB=A9=94=EC=9D=BC=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EB=B0=8F=20=EB=A1=9C=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/email-images/logo.png | Bin 0 -> 12521 bytes .../templates/email/admin-payout-request.html | 100 ++++++++++++++++++ .../templates/email/admin-refund-request.html | 51 +++++++++ .../templates/email/commission-cancelled.html | 38 +++++++ .../email/commission-finalized-designer.html | 47 ++++++++ .../commission-finalized-instructor.html | 59 +++++++++++ .../email/commission-matched-designer.html | 45 ++++++++ .../email/commission-matched-instructor.html | 49 +++++++++ .../commission-shortfall-instructor.html | 62 +++++++++++ .../templates/email/deposit-notification.html | 50 +++++++++ .../email/designer-signup-notification.html | 54 ++++++++++ .../email/final-cancelled-designer.html | 37 +++++++ .../email/final-cancelled-instructor.html | 38 +++++++ .../first-draft-all-submitted-instructor.html | 45 ++++++++ .../email/first-draft-missed-designer.html | 35 ++++++ .../email/first-draft-rejected-designer.html | 35 ++++++ .../email/first-draft-selected-designer.html | 41 +++++++ .../first-draft-shortfall-instructor.html | 62 +++++++++++ .../email/first-draft-zero-instructor.html | 48 +++++++++ .../email/revision-requested-designer.html | 49 +++++++++ .../email/revision-submitted-instructor.html | 45 ++++++++ .../templates/email/verification-code.html | 27 +++++ 22 files changed, 1017 insertions(+) create mode 100644 src/main/resources/email-images/logo.png create mode 100644 src/main/resources/templates/email/admin-payout-request.html create mode 100644 src/main/resources/templates/email/admin-refund-request.html create mode 100644 src/main/resources/templates/email/commission-cancelled.html create mode 100644 src/main/resources/templates/email/commission-finalized-designer.html create mode 100644 src/main/resources/templates/email/commission-finalized-instructor.html create mode 100644 src/main/resources/templates/email/commission-matched-designer.html create mode 100644 src/main/resources/templates/email/commission-matched-instructor.html create mode 100644 src/main/resources/templates/email/commission-shortfall-instructor.html create mode 100644 src/main/resources/templates/email/deposit-notification.html create mode 100644 src/main/resources/templates/email/designer-signup-notification.html create mode 100644 src/main/resources/templates/email/final-cancelled-designer.html create mode 100644 src/main/resources/templates/email/final-cancelled-instructor.html create mode 100644 src/main/resources/templates/email/first-draft-all-submitted-instructor.html create mode 100644 src/main/resources/templates/email/first-draft-missed-designer.html create mode 100644 src/main/resources/templates/email/first-draft-rejected-designer.html create mode 100644 src/main/resources/templates/email/first-draft-selected-designer.html create mode 100644 src/main/resources/templates/email/first-draft-shortfall-instructor.html create mode 100644 src/main/resources/templates/email/first-draft-zero-instructor.html create mode 100644 src/main/resources/templates/email/revision-requested-designer.html create mode 100644 src/main/resources/templates/email/revision-submitted-instructor.html create mode 100644 src/main/resources/templates/email/verification-code.html diff --git a/src/main/resources/email-images/logo.png b/src/main/resources/email-images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..355f7f9ce6a6a92b62e341a04aeb44cba5de0178 GIT binary patch literal 12521 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 DV%(+% literal 0 HcmV?d00001 diff --git a/src/main/resources/templates/email/admin-payout-request.html b/src/main/resources/templates/email/admin-payout-request.html new file mode 100644 index 0000000..7be2e74 --- /dev/null +++ b/src/main/resources/templates/email/admin-payout-request.html @@ -0,0 +1,100 @@ + + + + + + +
+ +
+ ditda +
+ +
+

디자이너 정산 요청

+

+ + 외주가 자동 최종 확정되어 디자이너 정산이 필요합니다. + 외주가 강사 수동 확정되어 디자이너 정산이 필요합니다. + 외주가 강사 미선택으로 취소되어 제출 디자이너에 대한 기본금 정산이 필요합니다. + 강사가 시안을 선택하여 미선택 디자이너에 대한 기본금 정산이 필요합니다. +
+ 어드민 Swagger에서 디자이너 계좌 정보를 조회 후 송금해 주세요. +

+
+ + +
+

+ 📌 정산 안내
+ 계좌 정보는 보안상 메일에 포함되지 않습니다.
+ 어드민 Swagger에서 디자이너 ID로 계좌 정보를 조회하세요. +

+
+ + +
+

📋 외주 정보

+
+ 외주 ID + +
+
+ 외주명 + +
+
+ 강사 + +
+
+ + +
+

+ 💰 정산 대상 + +

+ +
+
+ 디자이너 ID + +
+
+ 디자이너 + +
+
+ 정산 금액 + +
+
+ +
+
+ 총 정산 금액 + +
+
+
+ + + +

+ 본 메일은 시스템에 의해 자동 발송된 관리자 전용 알림입니다. +

+
+ + diff --git a/src/main/resources/templates/email/admin-refund-request.html b/src/main/resources/templates/email/admin-refund-request.html new file mode 100644 index 0000000..08b6d3d --- /dev/null +++ b/src/main/resources/templates/email/admin-refund-request.html @@ -0,0 +1,51 @@ + + + + + + +
+
+ ditda +
+
+

외주 마감 환불 처리 요청

+

+ 외주 마감 또는 취소로 인해 정산(환불) 사유가 발생했습니다.
+ 아래 내역을 확인하신 후 수동 환불을 진행해주시기 바랍니다. +

+
+
+
+ 외주 ID + +
+
+ 외주명 + +
+
+ 강사명 + +
+
+ 강사 이메일 + +
+
+ 마감 형태 + 지원자 0명 (외주 취소) + 정원 미달 (부분 환불 진행) +
+
+ 환불 요청액 + +
+
+

+ 본 메일은 시스템에 의해 자동 발송된 관리자 전용 알림입니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-cancelled.html b/src/main/resources/templates/email/commission-cancelled.html new file mode 100644 index 0000000..f40eac3 --- /dev/null +++ b/src/main/resources/templates/email/commission-cancelled.html @@ -0,0 +1,38 @@ + + + + + + +
+
+ ditda +
+
+

외주 매칭 취소 안내

+

+

+ 신청하신 외주에 기간 내에 지원한 디자이너가 없어
+ 아쉽게도 해당 외주가 자동 취소되었습니다.

+ [환불 안내]
+ 본 메일의 답장으로 환불받으실 은행과 계좌번호, 예금주명을 보내주시면,
+ 확인 후 영업일 기준 2~3일 이내에 전액 환불해 드릴 예정입니다. +

+
+
+
+ 외주명 + +
+
+ 진행 상태 + 취소 (환불 예정) +
+
+

+ ※ 본 메일은 환불 계좌 회신용으로 답장 수신이 가능합니다.
환불 계좌 외의 일반 문의사항이 있으시면 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-finalized-designer.html b/src/main/resources/templates/email/commission-finalized-designer.html new file mode 100644 index 0000000..ee26839 --- /dev/null +++ b/src/main/resources/templates/email/commission-finalized-designer.html @@ -0,0 +1,47 @@ + + + + + + +
+ +
+ ditda +
+ +
+

외주 최종 확정

+

+

+ + 작업하신 외주의 최종 마감일이 지나
+ 가장 최근 제출하신 시안이 자동으로 최종 확정되었습니다. +
+ + 작업하신 외주가 최종 확정되었습니다.
+ 강사님께서 시안을 최종 결과물로 확정하셨습니다. +
+

+ 소중한 시간을 내어 시안을 제출해 주신 점 감사드립니다.
+ 인센티브는 영업일 기준 2~3일 이내에 지급될 예정입니다. +

+
+ +
+
+ 외주명 + +
+
+ 진행 상태 + 최종 확정 완료 +
+
+ +

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-finalized-instructor.html b/src/main/resources/templates/email/commission-finalized-instructor.html new file mode 100644 index 0000000..36576e9 --- /dev/null +++ b/src/main/resources/templates/email/commission-finalized-instructor.html @@ -0,0 +1,59 @@ + + + + + + +
+ +
+ ditda +
+ +
+

외주 최종 확정

+

+

+ + 신청하신 외주의 최종 마감일이 지나
+ 가장 최근 제출된 시안이 자동으로 최종 확정되었습니다. +
+ + 신청하신 외주가 최종 확정되었습니다.
+ 선택하신 시안이 최종 결과물로 확정됩니다. +
+

+ 확정된 시안은 검토를 거쳐
+ 영업일 기준 2~3일 내에 최종 확정본을 메일로 발송해 드립니다. +

+ ditda를 + 이용해 주셔서 진심으로 감사드립니다. +

+ + +
+ +
+
+ 외주명 + +
+
+ 진행 상태 + 최종 확정 완료 +
+
+ + + +

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-matched-designer.html b/src/main/resources/templates/email/commission-matched-designer.html new file mode 100644 index 0000000..7113620 --- /dev/null +++ b/src/main/resources/templates/email/commission-matched-designer.html @@ -0,0 +1,45 @@ + + + + + + +
+ +
+ ditda +
+ +
+

1차 시안 대상자 선정 알림

+

+

+ 지원하신 외주 프로젝트의 1차 시안 대상자로 선정되셨습니다.
+ 기한 내에 고품질의 1차 시안을 제출해 주시기 바랍니다. +

+
+ +
+
+ 외주명 + +
+
+ 1차 시안 제출 마감일 + +
+
+ + + +

+ 마감 기한을 넘길 경우 매칭 불이익을 받을 수 있으니 기한 엄수 부탁드립니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-matched-instructor.html b/src/main/resources/templates/email/commission-matched-instructor.html new file mode 100644 index 0000000..1540699 --- /dev/null +++ b/src/main/resources/templates/email/commission-matched-instructor.html @@ -0,0 +1,49 @@ + + + + + + +
+ +
+ ditda +
+ +
+

디자이너 매칭 완료!

+

+

+ 신청하신 외주의 디자이너 모집 기간이 마감되어 매칭이 성공적으로 완료되었습니다.
+ 디자이너들의 1차 시안 제작이 완료되는 대로 알림을 보내드리겠습니다. +

+
+ +
+
+ 외주명 + +
+
+ 모집 디자이너 수 + +
+
+ 매칭 디자이너 수 + +
+
+ + + +

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/commission-shortfall-instructor.html b/src/main/resources/templates/email/commission-shortfall-instructor.html new file mode 100644 index 0000000..683f94e --- /dev/null +++ b/src/main/resources/templates/email/commission-shortfall-instructor.html @@ -0,0 +1,62 @@ + + + + + + +
+ +
+ ditda +
+ +
+

모집 인원 미달 안내

+

+

+ 신청하신 외주의 모집 인원이 모두 채워지지 않아
+ 매칭된 디자이너만으로 외주가 진행됩니다.

+ [부분 환불 안내]
+ 본 메일의 답장으로 환불받으실 은행과 계좌번호, 예금주명을 보내주시면,
+ 확인 후 영업일 기준 2~3일 이내에 미달 인원분에 해당하는 금액을 환불해 드릴 예정입니다. +

+
+ +
+
+ 외주명 + +
+
+ 모집 정원 + +
+
+ 매칭 인원 + +
+
+ 미달 인원 + +
+
+ 환불 예정 금액 + +
+
+ + + +

+ ※ 본 메일은 환불 계좌 회신용으로 답장 수신이 가능합니다.
+ 환불 계좌 외의 일반 문의사항은 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/deposit-notification.html b/src/main/resources/templates/email/deposit-notification.html new file mode 100644 index 0000000..f388477 --- /dev/null +++ b/src/main/resources/templates/email/deposit-notification.html @@ -0,0 +1,50 @@ + + + + + + +
+ +
+ ditda +
+ +
+

외주 입금 확인 요청

+

강사가 입금 완료를 통보했습니다.
입금 내역 확인 후 진행 처리 부탁드립니다.

+
+ +
+
+ 외주 ID + +
+
+ 외주명 + +
+
+ 강사 + +
+
+ 입금자명 + +
+
+ 금액 + +
+
+ 통보 시각 + +
+
+ +

+ 본 메일은 자동 발송된 알림입니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/designer-signup-notification.html b/src/main/resources/templates/email/designer-signup-notification.html new file mode 100644 index 0000000..0b0e88f --- /dev/null +++ b/src/main/resources/templates/email/designer-signup-notification.html @@ -0,0 +1,54 @@ + + + + + + +
+ +
+ ditda +
+ +
+

새 디자이너 가입 검토 요청

+

새로운 디자이너가 회원가입을 완료했습니다.
포트폴리오 검토 후 승인 처리 부탁드립니다.

+
+ + +
+

+ 📌 포트폴리오 확인 안내
+ 포트폴리오는 메일에 포함되지 않습니다.
+ 어드민 Swagger에서 디자이너 ID로 포트폴리오를 조회하세요. +

+
+ +
+
+ Designer ID + +
+
+ 이름 + +
+
+ 이메일 + +
+
+ + + +

+ 본 메일은 자동 발송된 알림입니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/final-cancelled-designer.html b/src/main/resources/templates/email/final-cancelled-designer.html new file mode 100644 index 0000000..a14aea7 --- /dev/null +++ b/src/main/resources/templates/email/final-cancelled-designer.html @@ -0,0 +1,37 @@ + + + + + + +
+
+ ditda +
+
+

외주 취소 안내

+

+

+ 제출하신 1차 시안에 대해 강사님의 최종 선택이 기한 내에 이루어지지 않아
+ 해당 외주가 자동 취소되었습니다.

+ 소중한 시간을 내어 시안을 제출해 주신 점 감사드립니다.
+ 기본금은 영업일 기준 2~3일 이내에 지급될 예정입니다. +

+
+
+
+ 외주명 + +
+
+ 진행 상태 + 취소 +
+
+

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/final-cancelled-instructor.html b/src/main/resources/templates/email/final-cancelled-instructor.html new file mode 100644 index 0000000..870c4af --- /dev/null +++ b/src/main/resources/templates/email/final-cancelled-instructor.html @@ -0,0 +1,38 @@ + + + + + + +
+
+ ditda +
+
+

외주 자동 취소 안내

+

+

+ 신청하신 외주의 최종 마감일까지 시안 선택이 이루어지지 않아
+ 해당 외주가 자동 취소되었습니다.

+ 기한 내 시안을 선택하지 않으신 점에 대한 안내로,
+ 본 건은 환불 대상이 아닙니다. +

+
+
+
+ 외주명 + +
+
+ 진행 상태 + 취소 (환불 없음) +
+
+

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다.
+ 문의사항은 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-all-submitted-instructor.html b/src/main/resources/templates/email/first-draft-all-submitted-instructor.html new file mode 100644 index 0000000..46da8a0 --- /dev/null +++ b/src/main/resources/templates/email/first-draft-all-submitted-instructor.html @@ -0,0 +1,45 @@ + + + + + + +
+ +
+ ditda +
+ +
+

1차 시안 제출 완료 안내

+

+

+ 신청하신 외주의 모든 디자이너가 1차 시안 제출을 완료했습니다.
+ 제출된 시안들을 확인하시고 최종 디자이너를 선택해 주세요. +

+
+ +
+
+ 외주명 + +
+
+ 제출 시안 + +
+
+ + + +

+ ※ 본 메일은 발신 전용입니다. 문의사항은 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-missed-designer.html b/src/main/resources/templates/email/first-draft-missed-designer.html new file mode 100644 index 0000000..375aff2 --- /dev/null +++ b/src/main/resources/templates/email/first-draft-missed-designer.html @@ -0,0 +1,35 @@ + + + + + + +
+
+ ditda +
+
+

1차 시안 기한 초과 안내

+

+

+ 지원하신 외주의 1차 시안 마감일까지 시안을 제출하지 않으셔서
+ 본 외주에서 자동 탈락 처리되었습니다.

+ ditda에서 다시 만나뵐 수 있기를 기대합니다. +

+
+
+
+ 외주명 + +
+
+ 처리 결과 + 자동 탈락 +
+
+

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-rejected-designer.html b/src/main/resources/templates/email/first-draft-rejected-designer.html new file mode 100644 index 0000000..d095c27 --- /dev/null +++ b/src/main/resources/templates/email/first-draft-rejected-designer.html @@ -0,0 +1,35 @@ + + + + + + +
+
+ ditda +
+
+

시안 선택 결과 안내

+

+

+ 제출하신 1차 시안이 아쉽게도 이번 외주에서 선택되지 않았습니다.

+ 소중한 시간을 내어 시안을 제출해 주신 점 감사드립니다.
+ 기본금은 영업일 기준 2~3일 이내에 지급될 예정입니다. +

+
+
+
+ 외주명 + +
+
+ 결과 + 미선택 +
+
+

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-selected-designer.html b/src/main/resources/templates/email/first-draft-selected-designer.html new file mode 100644 index 0000000..1f52e3f --- /dev/null +++ b/src/main/resources/templates/email/first-draft-selected-designer.html @@ -0,0 +1,41 @@ + + + + + + +
+
+ ditda +
+
+

시안 선택 안내

+

+

+ 제출하신 1차 시안이 최종 선택되었습니다!
+ 앞으로 강사님과 함께 수정 진행이 시작됩니다.

+ 대시보드에서 수정 요청을 확인해 주세요. +

+
+
+
+ 외주명 + +
+
+ 진행 상태 + 최종 선택 완료 +
+
+ +

+ 본 메일은 수신전용 메일이므로 회신되지 않습니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-shortfall-instructor.html b/src/main/resources/templates/email/first-draft-shortfall-instructor.html new file mode 100644 index 0000000..144ae01 --- /dev/null +++ b/src/main/resources/templates/email/first-draft-shortfall-instructor.html @@ -0,0 +1,62 @@ + + + + + + +
+ +
+ ditda +
+ +
+

1차 시안 일부 미제출 안내

+

+

+ 신청하신 외주의 1차 시안 마감일까지 일부 디자이너의 시안이 제출되지 않았습니다.
+ 제출된 시안들 중에서 최종 선택을 진행해 주세요.

+ [부분 환불 안내]
+ 본 메일의 답장으로 환불받으실 은행과 계좌번호, 예금주명을 보내주시면,
+ 확인 후 영업일 기준 2~3일 이내에 미달 인원분에 해당하는 금액을 환불해 드릴 예정입니다. +

+
+ +
+
+ 외주명 + +
+
+ 제출 시안 + +
+
+ 미제출 + +
+
+ 환불 예정 금액 + +
+
+ 최종 마감일 + +
+
+ + + +

+ ※ 본 메일은 환불 계좌 회신용으로 답장 수신이 가능합니다.
+ 환불 계좌 외의 일반 문의사항은 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/first-draft-zero-instructor.html b/src/main/resources/templates/email/first-draft-zero-instructor.html new file mode 100644 index 0000000..dca7079 --- /dev/null +++ b/src/main/resources/templates/email/first-draft-zero-instructor.html @@ -0,0 +1,48 @@ + + + + + + +
+ +
+ ditda +
+ +
+

외주 자동 취소 안내

+

+

+ 신청하신 외주의 1차 시안 마감일까지 제출된 시안이 없어
+ 아쉽게도 해당 외주가 자동 취소되었습니다.

+ [환불 안내]
+ 본 메일의 답장으로 환불받으실 은행과 계좌번호, 예금주명을 보내주시면,
+ 확인 후 영업일 기준 2~3일 이내에 전액 환불해 드릴 예정입니다. +

+
+ +
+
+ 외주명 + +
+
+ 진행 상태 + 취소 (환불 예정) +
+
+ 환불 금액 + +
+
+ +

+ ※ 본 메일은 환불 계좌 회신용으로 답장 수신이 가능합니다.
+ 환불 계좌 외의 일반 문의사항은 고객센터로 연락 바랍니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/revision-requested-designer.html b/src/main/resources/templates/email/revision-requested-designer.html new file mode 100644 index 0000000..5bf9529 --- /dev/null +++ b/src/main/resources/templates/email/revision-requested-designer.html @@ -0,0 +1,49 @@ + + + + + + +
+ +
+ ditda +
+ +
+

시안 수정 요청 안내

+

+

+ 강사님으로부터 시안 수정 요청이 도착했습니다.
+ 요청 내용을 확인하시고 가능한 빠르게 수정본을 제출해 주세요. +

+
+ +
+
+ 외주명 + +
+
+ 수정 차수 + +
+
+ + + +

+ 제출이 지연되면 남은 수정 기회에 영향을 줄 수 있으니 신속히 진행해 주세요. +

+ +

+ 본 메일은 발신 전용입니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/revision-submitted-instructor.html b/src/main/resources/templates/email/revision-submitted-instructor.html new file mode 100644 index 0000000..dfc086a --- /dev/null +++ b/src/main/resources/templates/email/revision-submitted-instructor.html @@ -0,0 +1,45 @@ + + + + + + +
+ +
+ ditda +
+ +
+

수정본 제출 안내

+

+

+ 요청하신 수정 사항에 대한 수정본이 제출되었습니다.
+ 제출된 수정본을 확인해 주세요. +

+
+ +
+
+ 외주명 + +
+
+ 수정 차수 + +
+
+ + + +

+ 본 메일은 발신 전용입니다. +

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/email/verification-code.html b/src/main/resources/templates/email/verification-code.html new file mode 100644 index 0000000..120e327 --- /dev/null +++ b/src/main/resources/templates/email/verification-code.html @@ -0,0 +1,27 @@ + + + + + Ditda 이메일 인증 + + +
+ + +
+ ditda +
+ +
+

ditda 이메일 인증번호

+

아래 인증번호를 입력해주세요.

+
+ 0000 +
+

+ 인증번호는 5분간 유효합니다. +

+
+
+ + \ No newline at end of file From ce6ede349f9cf76880305f1073ec4e1eb33f1250 Mon Sep 17 00:00:00 2001 From: fervovita Date: Wed, 15 Jul 2026 01:47:06 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20=EB=A9=94=EC=9D=BC=20=EB=B0=9C?= =?UTF-8?q?=EC=86=A1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 8 +- scripts/deploy.sh | 6 ++ .../ditda/notification/mail/EmailSender.java | 62 ++++++++++++++ .../ditda/notification/mail/MailListener.java | 12 ++- .../ditda/notification/mail/MailMasker.java | 26 ++++++ .../notification/mail/NotificationType.java | 81 +++++++++++++++++++ src/main/resources/application.yaml | 17 +++- src/test/resources/application-test.yaml | 7 +- 8 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ditda/notification/mail/EmailSender.java create mode 100644 src/main/java/ditda/notification/mail/MailMasker.java create mode 100644 src/main/java/ditda/notification/mail/NotificationType.java diff --git a/.env.example b/.env.example index a44ae1a..2f5da66 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,10 @@ RABBITMQ_HOST=localhost RABBITMQ_PORT=5672 RABBITMQ_USERNAME=your_username -RABBITMQ_PASSWORD=your_password \ No newline at end of file +RABBITMQ_PASSWORD=your_password + +# Mail +MAIL_HOST=smtp.gmail.com +MAIL_PORT=587 +MAIL_USERNAME=your_mail +MAIL_PASSWORD=your_mail_password \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3b60800..f21eb7f 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -54,6 +54,12 @@ REQUIRED_VARS=( "RABBITMQ_PORT" "RABBITMQ_USERNAME" "RABBITMQ_PASSWORD" + +# Mail +"MAIL_HOST" +"MAIL_PORT" +"MAIL_USERNAME" +"MAIL_PASSWORD" ) MISSING=() diff --git a/src/main/java/ditda/notification/mail/EmailSender.java b/src/main/java/ditda/notification/mail/EmailSender.java new file mode 100644 index 0000000..bca9d3a --- /dev/null +++ b/src/main/java/ditda/notification/mail/EmailSender.java @@ -0,0 +1,62 @@ +package ditda.notification.mail; + +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import org.springframework.boot.mail.autoconfigure.MailProperties; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class EmailSender { + + private static final String LOGO_CID = "logoImage"; + private static final String LOGO_PATH = "email-images/logo.png"; + + private static final String FROM_NAME = "DITDA"; + + private final JavaMailSender mailSender; + private final SpringTemplateEngine templateEngine; + private final MailProperties mailProperties; + + public void send(String to, NotificationType type, Map variables) + throws MessagingException { + + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + + try { + helper.setFrom(new InternetAddress(mailProperties.getUsername(), FROM_NAME, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 is not supported", e); + } + + helper.setTo(to); + helper.setSubject(type.getSubject()); + helper.setText(renderTemplate(type.getTemplate(), variables), true); + + helper.addInline(LOGO_CID, new ClassPathResource(LOGO_PATH)); + + mailSender.send(mimeMessage); + + log.info("[Email] Email sent. to={}, type={}", MailMasker.mask(to), type); + } + + private String renderTemplate(String templateName, Map variables) { + Context context = new Context(); + variables.forEach(context::setVariable); + return templateEngine.process(templateName, context); + } +} diff --git a/src/main/java/ditda/notification/mail/MailListener.java b/src/main/java/ditda/notification/mail/MailListener.java index a93ebec..675446b 100644 --- a/src/main/java/ditda/notification/mail/MailListener.java +++ b/src/main/java/ditda/notification/mail/MailListener.java @@ -3,14 +3,22 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; +import jakarta.mail.MessagingException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @Component +@RequiredArgsConstructor public class MailListener { + private final EmailSender emailSender; + @RabbitListener(queues = MailRabbitConfig.MAIL_QUEUE) - public void handle(MailMessage message) { - log.info("메일 메세지 수신. type: {}, to: {}", message.type(), message.to()); + public void handle(MailMessage message) throws MessagingException { + log.info("[Email] Mail message received. type: {}, to: {}", message.type(), MailMasker.mask(message.to())); + + NotificationType type = NotificationType.valueOf(message.type()); + emailSender.send(message.to(), type, message.variables()); } } diff --git a/src/main/java/ditda/notification/mail/MailMasker.java b/src/main/java/ditda/notification/mail/MailMasker.java new file mode 100644 index 0000000..98e646a --- /dev/null +++ b/src/main/java/ditda/notification/mail/MailMasker.java @@ -0,0 +1,26 @@ +package ditda.notification.mail; + +import org.springframework.util.StringUtils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class MailMasker { + + public static String mask(String email) { + if (!StringUtils.hasText(email) || !email.contains("@")) { + return "***"; + } + + int at = email.indexOf("@"); + String local = email.substring(0, at); + String domain = email.substring(at); + + if (local.length() <= 2) { + return "****" + domain; + } + + return local.substring(0, 2) + "****" + domain; + } +} diff --git a/src/main/java/ditda/notification/mail/NotificationType.java b/src/main/java/ditda/notification/mail/NotificationType.java new file mode 100644 index 0000000..95030b3 --- /dev/null +++ b/src/main/java/ditda/notification/mail/NotificationType.java @@ -0,0 +1,81 @@ +package ditda.notification.mail; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum NotificationType { + + // 지원 마감 (ApplicationDeadlineClosed) + APPLICATION_REFUND_REQUEST_ADMIN( + "[DITDA] 외주 마감에 따른 환불 처리 요망", "email/admin-refund-request"), + APPLICATION_CANCELLED_INSTRUCTOR( + "[DITDA] 신청하신 외주가 지원자 부족으로 취소되었습니다.", "email/commission-cancelled"), + APPLICATION_SHORTFALL_INSTRUCTOR( + "[DITDA] 신청하신 외주의 모집 인원이 미달되었습니다.", "email/commission-shortfall-instructor"), + APPLICATION_MATCHED_INSTRUCTOR( + "[DITDA] 신청하신 외주의 디자이너 매칭이 완료되었습니다.", "email/commission-matched-instructor"), + APPLICATION_MATCHED_DESIGNER( + "[DITDA] 지원하신 외주의 1차 시안 대상자로 선정되었습니다.", "email/commission-matched-designer"), + + // 1차 시안 마감 (FirstDraftDeadlineClosed) + FIRST_DRAFT_REFUND_REQUEST_ADMIN( + "[DITDA] 1차 시안 미제출에 따른 환불 처리 요망", "email/admin-refund-request"), + FIRST_DRAFT_ZERO_INSTRUCTOR( + "[DITDA] 1차 시안 제출이 없어 외주가 취소되었습니다.", "email/first-draft-zero-instructor"), + FIRST_DRAFT_SHORTFALL_INSTRUCTOR( + "[DITDA] 일부 디자이너의 1차 시안이 미제출되었습니다.", "email/first-draft-shortfall-instructor"), + FIRST_DRAFT_MISSED_DESIGNER( + "[DITDA] 1차 시안 기한 초과로 외주 진행이 종료되었습니다.", "email/first-draft-missed-designer"), + + // 최종 마감 (FinalDeadlineClosed) + FINAL_CANCELLED_INSTRUCTOR( + "[DITDA] 시안 선택 기한 초과로 외주가 취소되었습니다.", "email/final-cancelled-instructor"), + FINAL_CANCELLED_DESIGNER( + "[DITDA] 강사 미선택으로 외주가 취소되었습니다.", "email/final-cancelled-designer"), + + // 전원 1차 시안 제출 (AllFirstDraftsSubmitted) + ALL_FIRST_DRAFTS_SUBMITTED_INSTRUCTOR( + "[DITDA] 모든 1차 시안이 제출되었습니다. 시안을 선택해 주세요.", "email/first-draft-all-submitted-instructor"), + + // 시안 선택 (DraftSelected) + DRAFT_SELECTED_DESIGNER( + "[DITDA] 제출하신 1차 시안이 최종 선택되었습니다.", "email/first-draft-selected-designer"), + DRAFT_REJECTED_DESIGNER( + "[DITDA] 1차 시안 선정 결과를 안내해 드립니다.", "email/first-draft-rejected-designer"), + + // 외주 최종 확정 (CommissionCompleted) + COMMISSION_FINALIZED_INSTRUCTOR( + "[DITDA] 신청하신 외주가 최종 확정되었습니다.", "email/commission-finalized-instructor"), + COMMISSION_FINALIZED_DESIGNER( + "[DITDA] 작업하신 외주가 최종 확정되었습니다.", "email/commission-finalized-designer"), + + // 수정 요청/제출 (RevisionRequested / RevisionSubmitted) + REVISION_REQUESTED_DESIGNER( + "[DITDA] 시안 수정 요청이 도착했습니다. 확인해주세요.", "email/revision-requested-designer"), + REVISION_SUBMITTED_INSTRUCTOR( + "[DITDA] 수정본이 제출되었습니다. 확인해주세요.", "email/revision-submitted-instructor"), + + // 정산 요청 (PayoutRequested) + PAYOUT_REQUEST_COMPLETED_ADMIN( + "[DITDA] 외주 최종 확정 - 디자이너 정산 요청", "email/admin-payout-request"), + PAYOUT_REQUEST_CANCELLED_ADMIN( + "[DITDA] 외주 취소 - 제출 디자이너 정산 요청", "email/admin-payout-request"), + PAYOUT_REQUEST_REJECTED_ADMIN( + "[DITDA] 시안 선택 완료 - 미선택 디자이너 정산 요청", "email/admin-payout-request"), + + // auth + EMAIL_VERIFICATION( + "[DITDA] 이메일 인증 코드", "email/verification-code"), + DESIGNER_SIGNUP_REVIEW_ADMIN( + "[DITDA] 새 디자이너 가입 검토 요청", "email/designer-signup-notification"), + + // payment + DEPOSIT_CONFIRM_REQUEST_ADMIN( + "[DITDA] 외주 입금 확인 요청", "email/deposit-notification"); + + private final String subject; + private final String template; +} + diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 41a284a..677d1da 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -19,5 +19,20 @@ spring: initial-interval: 2s multiplier: 2 max-interval: 10s - max-retries: 3 + max-retries: 2 default-requeue-rejected: false + + mail: + host: ${MAIL_HOST} + port: ${MAIL_PORT:587} + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + connectiontimeout: 5000 + timeout: 10000 + writetimeout: 10000 diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 6818a22..d505587 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -2,4 +2,9 @@ spring: rabbitmq: listener: simple: - auto-startup: false \ No newline at end of file + auto-startup: false + + mail: + host: localhost + username: test + password: test \ No newline at end of file From 226cd2f4fd0670998365abe1cb12186dde1eaad7 Mon Sep 17 00:00:00 2001 From: fervovita Date: Wed, 15 Jul 2026 02:00:30 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Fix:=20EmailSender=20null=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ditda/notification/mail/EmailSender.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ditda/notification/mail/EmailSender.java b/src/main/java/ditda/notification/mail/EmailSender.java index bca9d3a..c885541 100644 --- a/src/main/java/ditda/notification/mail/EmailSender.java +++ b/src/main/java/ditda/notification/mail/EmailSender.java @@ -56,7 +56,10 @@ public void send(String to, NotificationType type, Map variables private String renderTemplate(String templateName, Map variables) { Context context = new Context(); - variables.forEach(context::setVariable); + if (variables != null) { + variables.forEach(context::setVariable); + } + return templateEngine.process(templateName, context); } } From e3ac4a3a7f2475d7c123fac487167e2f9cbd04ab Mon Sep 17 00:00:00 2001 From: fervovita Date: Wed, 15 Jul 2026 02:08:07 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Fix:=20=EB=A9=94=EC=9D=BC=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EA=B8=88=EC=95=A1=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EC=9E=90=EB=A6=BF=EC=88=98=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/email/admin-payout-request.html | 4 ++-- src/main/resources/templates/email/admin-refund-request.html | 2 +- .../templates/email/commission-shortfall-instructor.html | 2 +- src/main/resources/templates/email/deposit-notification.html | 2 +- .../templates/email/first-draft-shortfall-instructor.html | 2 +- .../templates/email/first-draft-zero-instructor.html | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/resources/templates/email/admin-payout-request.html b/src/main/resources/templates/email/admin-payout-request.html index 7be2e74..ce0b0c1 100644 --- a/src/main/resources/templates/email/admin-payout-request.html +++ b/src/main/resources/templates/email/admin-payout-request.html @@ -72,7 +72,7 @@

디자이너 정산 요청

정산 금액 + th:text="${#numbers.formatInteger(payout.amount, 1, 'COMMA')} + '원'">
@@ -80,7 +80,7 @@

디자이너 정산 요청

총 정산 금액 + th:text="${#numbers.formatInteger(totalAmount, 1, 'COMMA')} + '원'">
diff --git a/src/main/resources/templates/email/admin-refund-request.html b/src/main/resources/templates/email/admin-refund-request.html index 08b6d3d..015b036 100644 --- a/src/main/resources/templates/email/admin-refund-request.html +++ b/src/main/resources/templates/email/admin-refund-request.html @@ -40,7 +40,7 @@

외주 마감 환불 처리 요청

환불 요청액 + th:text="${#numbers.formatInteger(refundAmount, 1, 'COMMA')} + '원'">

diff --git a/src/main/resources/templates/email/commission-shortfall-instructor.html b/src/main/resources/templates/email/commission-shortfall-instructor.html index 683f94e..510dad4 100644 --- a/src/main/resources/templates/email/commission-shortfall-instructor.html +++ b/src/main/resources/templates/email/commission-shortfall-instructor.html @@ -42,7 +42,7 @@

모집 인원 미달 안내

환불 예정 금액 + th:text="${#numbers.formatInteger(refundAmount, 1, 'COMMA')} + '원'">
diff --git a/src/main/resources/templates/email/deposit-notification.html b/src/main/resources/templates/email/deposit-notification.html index f388477..b5b4f8d 100644 --- a/src/main/resources/templates/email/deposit-notification.html +++ b/src/main/resources/templates/email/deposit-notification.html @@ -34,7 +34,7 @@

외주 입금 확인 요청

금액 - +
통보 시각 diff --git a/src/main/resources/templates/email/first-draft-shortfall-instructor.html b/src/main/resources/templates/email/first-draft-shortfall-instructor.html index 144ae01..5965824 100644 --- a/src/main/resources/templates/email/first-draft-shortfall-instructor.html +++ b/src/main/resources/templates/email/first-draft-shortfall-instructor.html @@ -38,7 +38,7 @@

1차 시안 일부 미제출 안내

환불 예정 금액 + th:text="${#numbers.formatInteger(refundAmount, 1, 'COMMA')} + '원'">
최종 마감일 diff --git a/src/main/resources/templates/email/first-draft-zero-instructor.html b/src/main/resources/templates/email/first-draft-zero-instructor.html index dca7079..77f640a 100644 --- a/src/main/resources/templates/email/first-draft-zero-instructor.html +++ b/src/main/resources/templates/email/first-draft-zero-instructor.html @@ -35,7 +35,7 @@

외주 자동 취소 안내

환불 금액 + th:text="${#numbers.formatInteger(refundAmount, 1, 'COMMA')} + '원'">
From 17b18b0df826483c39cb0fc20f92bf7e3c2d8bdd Mon Sep 17 00:00:00 2001 From: fervovita Date: Wed, 15 Jul 2026 02:34:04 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=ED=99=98=EB=B6=88=20=EC=9A=94=EC=B2=AD=20=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=82=AC=EC=9C=A0=EB=B3=84=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/templates/email/admin-refund-request.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/email/admin-refund-request.html b/src/main/resources/templates/email/admin-refund-request.html index 015b036..ceeba99 100644 --- a/src/main/resources/templates/email/admin-refund-request.html +++ b/src/main/resources/templates/email/admin-refund-request.html @@ -34,8 +34,12 @@

외주 마감 환불 처리 요청

마감 형태 - 지원자 0명 (외주 취소) - 정원 미달 (부분 환불 진행) + + 지원자 0명 (외주 취소) + 정원 미달 (부분 환불 진행) + 1차 시안 전원 미제출 (외주 취소) + 일부 시안 미제출 (부분 환불 진행) +
환불 요청액