"""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