260706_1_계정, 로그인, 보안 초안 작성
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""표준 라이브러리 기반 비동기 SMTP 발송 서비스."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr
|
||||
|
||||
from config.config_system import (
|
||||
EMAIL_FROM_ADDRESS,
|
||||
EMAIL_FROM_NAME,
|
||||
EMAIL_NOTIFICATION_ENABLED,
|
||||
EMAIL_RETRY_COUNT,
|
||||
EMAIL_RETRY_DELAY_SECONDS,
|
||||
SMTP_HOST,
|
||||
SMTP_PASSWORD,
|
||||
SMTP_PORT,
|
||||
SMTP_USE_TLS,
|
||||
SMTP_USERNAME,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _send_sync(to_email: str, subject: str, html: str) -> None:
|
||||
message = EmailMessage()
|
||||
message["From"] = formataddr((EMAIL_FROM_NAME, EMAIL_FROM_ADDRESS))
|
||||
message["To"] = to_email
|
||||
message["Subject"] = subject
|
||||
message.set_content("HTML 이메일을 지원하는 메일 앱에서 확인해 주세요.")
|
||||
message.add_alternative(html, subtype="html")
|
||||
|
||||
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as smtp:
|
||||
if SMTP_USE_TLS:
|
||||
smtp.starttls()
|
||||
smtp.login(SMTP_USERNAME, SMTP_PASSWORD)
|
||||
smtp.send_message(message)
|
||||
|
||||
|
||||
async def send_email(to_email: str, subject: str, html: str) -> bool:
|
||||
"""SMTP 발송을 워커 스레드에서 실행하고 설정된 횟수만큼 재시도한다."""
|
||||
if not EMAIL_NOTIFICATION_ENABLED:
|
||||
logger.info("이메일 발송 비활성화: %s", to_email)
|
||||
return True
|
||||
if not SMTP_USERNAME or not SMTP_PASSWORD or not EMAIL_FROM_ADDRESS:
|
||||
logger.error("SMTP 환경 변수가 설정되지 않았습니다.")
|
||||
return False
|
||||
|
||||
for attempt in range(1, EMAIL_RETRY_COUNT + 1):
|
||||
try:
|
||||
await asyncio.to_thread(_send_sync, to_email, subject, html)
|
||||
logger.info("이메일 발송 성공: %s", to_email)
|
||||
return True
|
||||
except (OSError, smtplib.SMTPException):
|
||||
logger.exception("이메일 발송 실패 (%s/%s)", attempt, EMAIL_RETRY_COUNT)
|
||||
if attempt < EMAIL_RETRY_COUNT:
|
||||
await asyncio.sleep(EMAIL_RETRY_DELAY_SECONDS * (2 ** (attempt - 1)))
|
||||
return False
|
||||
|
||||
|
||||
def send_email_background(to_email: str, subject: str, html: str) -> None:
|
||||
"""HTTP 응답을 차단하지 않고 이메일 발송을 예약한다."""
|
||||
task = asyncio.create_task(send_email(to_email, subject, html))
|
||||
task.add_done_callback(lambda completed: completed.exception())
|
||||
Reference in New Issue
Block a user