From 8d9b0ff2fa3273773c12501dfc6a913752d4cc30 Mon Sep 17 00:00:00 2001 From: zio Date: Mon, 1 Jun 2026 22:12:55 +0900 Subject: [PATCH] feat: add backend/smtp_client.py --- backend/smtp_client.py | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 backend/smtp_client.py diff --git a/backend/smtp_client.py b/backend/smtp_client.py new file mode 100644 index 00000000..1b1cc87e --- /dev/null +++ b/backend/smtp_client.py @@ -0,0 +1,59 @@ +"""SMTP 발송: smtplib STARTTLS + Sent 폴더 자동 저장""" +import smtplib, ssl, asyncio +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email import encoders +from email.utils import formatdate +from typing import Optional + +SMTP_HOST = "localhost" +SMTP_PORT = 587 + + +async def send_mail( + username: str, password: str, + to: str, subject: str, body: str, + cc: Optional[str] = None, bcc: Optional[str] = None, + is_html: bool = False, + attachments: Optional[list] = None, +) -> bytes: + """발송 성공 시 raw 메시지 바이트 반환 (Sent 폴더 저장용)""" + msg = MIMEMultipart("alternative" if is_html else "mixed") + msg["From"] = username + msg["To"] = to + msg["Subject"] = subject + msg["Date"] = formatdate(localtime=True) + if cc: msg["Cc"] = cc + + msg.attach(MIMEText(body, "html" if is_html else "plain", "utf-8")) + + if attachments: + for name, data, ctype in attachments: + part = MIMEBase(*ctype.split("/", 1)) + part.set_payload(data) + encoders.encode_base64(part) + part.add_header("Content-Disposition", "attachment", filename=name) + msg.attach(part) + + recipients = [r.strip() for r in to.split(",")] + if cc: recipients += [r.strip() for r in cc.split(",")] + if bcc: recipients += [r.strip() for r in bcc.split(",")] + + raw_bytes = msg.as_bytes() + user_short = username.split("@")[0] + + def _do(): + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + s = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) + s.ehlo() + s.starttls(context=ctx) + s.ehlo() + s.login(user_short, password) + s.sendmail(username, recipients, raw_bytes) + s.quit() + + await asyncio.get_event_loop().run_in_executor(None, _do) + return raw_bytes