Files
AutonetSellCar/backend/app/services/email_service.py
AutonetSellCar Deploy 46973c8508 feat: Add email notifications for all notification types
Sends HTML email via Gmail SMTP when notifications are created.
Supports multi-language (en/ko/mn/ru) based on user country.
Runs in background thread to avoid blocking requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 09:55:12 +09:00

188 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Email Notification Service
Sends email notifications to users for important events (recommendations, shipping, payments, etc.)
Reuses existing SMTP configuration (Gmail).
"""
import smtplib
import threading
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
from ..config import get_settings
settings = get_settings()
# Notification types that should trigger email
EMAIL_ENABLED_TYPES = {
"vehicle_recommended",
"shipping_update",
"payment_confirmed",
"withdrawal_processed",
"inquiry_reply",
"dealer_approved",
"dealer_rejected",
"referral_reward",
"share_purchased",
}
def get_user_language(country: Optional[str]) -> str:
"""Detect language from user's country"""
if not country:
return "en"
c = country.lower().strip()
if c in ("korea", "south korea", "kr", "한국"):
return "ko"
if c in ("mongolia", "mn", "монгол"):
return "mn"
if c in ("russia", "ru", "россия"):
return "ru"
return "en"
def _build_email_content(
notification_type: str,
title: str,
message: str,
link: Optional[str],
user_name: Optional[str],
language: str,
) -> tuple[str, str]:
"""Build email subject and HTML body for a notification.
Returns (subject, html_body).
"""
base_url = "https://autonetsellcar.com"
full_link = f"{base_url}{link}" if link else base_url
# Greeting by language
greetings = {
"en": f"Hi {user_name}," if user_name else "Hello,",
"ko": f"{user_name}님, 안녕하세요." if user_name else "안녕하세요,",
"mn": f"Сайн байна уу, {user_name}." if user_name else "Сайн байна уу,",
"ru": f"Здравствуйте, {user_name}." if user_name else "Здравствуйте,",
}
button_labels = {
"en": "View Details",
"ko": "자세히 보기",
"mn": "Дэлгэрэнгүй үзэх",
"ru": "Подробнее",
}
footers = {
"en": "You received this email because you have an account on AutonetSellCar.com",
"ko": "AutonetSellCar.com 계정이 있어 이 이메일을 받으셨습니다",
"mn": "Та AutonetSellCar.com-д бүртгэлтэй тул энэ имэйлийг хүлээн авлаа",
"ru": "Вы получили это письмо, так как у вас есть аккаунт на AutonetSellCar.com",
}
greeting = greetings.get(language, greetings["en"])
button_label = button_labels.get(language, button_labels["en"])
footer = footers.get(language, footers["en"])
subject = f"AutonetSellCar - {title}"
html_body = f"""\
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#f4f4f4;font-family:Arial,sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f4;padding:20px 0;">
<tr><td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">
<!-- Header -->
<tr>
<td style="background:#1a1a2e;padding:24px 32px;">
<h1 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">AutonetSellCar</h1>
</td>
</tr>
<!-- Body -->
<tr>
<td style="padding:32px;">
<p style="margin:0 0 16px;color:#333;font-size:15px;">{greeting}</p>
<h2 style="margin:0 0 12px;color:#1a1a2e;font-size:18px;">{title}</h2>
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">{message}</p>
<a href="{full_link}"
style="display:inline-block;background:#2563eb;color:#ffffff;text-decoration:none;padding:12px 28px;border-radius:6px;font-size:14px;font-weight:600;">
{button_label}
</a>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="padding:20px 32px;background:#f9fafb;border-top:1px solid #e5e7eb;">
<p style="margin:0;color:#9ca3af;font-size:12px;">{footer}</p>
</td>
</tr>
</table>
</td></tr>
</table>
</body>
</html>"""
return subject, html_body
def _send_smtp(to_email: str, subject: str, html_body: str) -> bool:
"""Send an email via SMTP. Returns True on success."""
try:
if not settings.SMTP_USER or not settings.SMTP_PASSWORD:
print(f"[DEV] Notification email to {to_email}: {subject}")
return True
msg = MIMEMultipart("alternative")
msg["From"] = f"{settings.SMTP_FROM_NAME} <{settings.SMTP_FROM_EMAIL or settings.SMTP_USER}>"
msg["To"] = to_email
msg["Subject"] = subject
msg.attach(MIMEText(html_body, "html", "utf-8"))
with smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT) as server:
server.starttls()
server.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
server.send_message(msg)
print(f"[INFO] Notification email sent to {to_email}: {subject}")
return True
except Exception as e:
print(f"[ERROR] Failed to send notification email to {to_email}: {e}")
return False
def send_notification_email(
to_email: str,
notification_type: str,
title: str,
message: str,
link: Optional[str] = None,
user_name: Optional[str] = None,
user_country: Optional[str] = None,
) -> None:
"""Send a notification email in a background thread (non-blocking).
Only sends for notification types in EMAIL_ENABLED_TYPES.
"""
if notification_type not in EMAIL_ENABLED_TYPES:
return
if not to_email:
return
language = get_user_language(user_country)
subject, html_body = _build_email_content(
notification_type=notification_type,
title=title,
message=message,
link=link,
user_name=user_name,
language=language,
)
# Send in background thread to avoid blocking the request
thread = threading.Thread(
target=_send_smtp,
args=(to_email, subject, html_body),
daemon=True,
)
thread.start()