260706_1_계정, 로그인, 보안 초안 작성

This commit is contained in:
2026-07-07 19:17:13 +09:00
parent 404941c47a
commit f8633bb1fe
4696 changed files with 6110 additions and 642171 deletions
+64
View File
@@ -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())