From 46973c850853c3c2b8b49438e46e6db490976933 Mon Sep 17 00:00:00 2001 From: AutonetSellCar Deploy Date: Wed, 18 Feb 2026 09:55:12 +0900 Subject: [PATCH] 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 --- backend/app/api/notification.py | 20 ++- backend/app/services/email_service.py | 187 ++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 backend/app/services/email_service.py diff --git a/backend/app/api/notification.py b/backend/app/api/notification.py index f521c86..04c1248 100644 --- a/backend/app/api/notification.py +++ b/backend/app/api/notification.py @@ -10,6 +10,7 @@ from ..schemas.notification import ( NotificationCreate, NotificationResponse, NotificationListResponse, NotificationMarkRead ) +from ..services.email_service import send_notification_email from .auth import get_current_user router = APIRouter(prefix="/notifications", tags=["notifications"]) @@ -29,7 +30,7 @@ def create_notification( related_id: Optional[int] = None, related_type: Optional[str] = None ) -> Notification: - """Create a new notification""" + """Create a new notification and send email""" notification = Notification( user_id=user_id, notification_type=notification_type, @@ -42,6 +43,23 @@ def create_notification( db.add(notification) db.commit() db.refresh(notification) + + # Send email notification (background thread, non-blocking) + try: + user = db.query(User).filter(User.id == user_id).first() + if user and user.email: + send_notification_email( + to_email=user.email, + notification_type=notification_type, + title=title, + message=message, + link=link, + user_name=user.name, + user_country=user.country, + ) + except Exception as e: + print(f"[WARN] Email notification failed for user {user_id}: {e}") + return notification diff --git a/backend/app/services/email_service.py b/backend/app/services/email_service.py new file mode 100644 index 0000000..c1cd87a --- /dev/null +++ b/backend/app/services/email_service.py @@ -0,0 +1,187 @@ +""" +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"""\ + + + + + + +
+ + + + + + + + + + + + + +
+

AutonetSellCar

+
+

{greeting}

+

{title}

+

{message}

+ + {button_label} + +
+

{footer}

+
+
+ +""" + + 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()