Arag / app /services /notifications.py
AuthorBot
Restructure project for HF Spaces deployment
772f852
Raw
History Blame Contribute Delete
2.75 kB
"""Author RAG β€” Notifications Service (SMTP Email).
Sends OTP login codes and system alerts via Gmail SMTP.
Per implementation plan feature #2: Author login OTP via email.
"""
import smtplib
import ssl
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import structlog
from app.config import get_settings
logger = structlog.get_logger(__name__)
cfg = get_settings()
async def send_login_otp(to_email: str, otp_code: str, author_name: str = "") -> bool:
"""Send a 6-digit OTP code to the author's email for login.
Args:
to_email: Recipient email address.
otp_code: 6-digit TOTP code.
author_name: Author's display name.
Returns:
True if sent successfully, False on failure.
"""
subject = f"Your {cfg.APP_NAME} Login Code: {otp_code}"
body = f"""
Hi {author_name or "there"},
Your login verification code is:
{otp_code}
This code expires in 30 seconds. Do not share it with anyone.
If you didn't request this, please ignore this email.
β€” {cfg.APP_NAME} Team
"""
return await _send_email(to_email, subject, body)
async def send_token_warning(to_email: str, author_name: str, usage_pct: float) -> bool:
"""Send a token budget warning when usage crosses a threshold."""
subject = f"[{cfg.APP_NAME}] Token Budget Warning β€” {usage_pct:.0%} Used"
body = f"""
Hi {author_name},
Your chatbot has used {usage_pct:.0%} of your monthly token budget.
Log in to your dashboard to view usage details or contact support to upgrade.
β€” {cfg.APP_NAME} Team
"""
return await _send_email(to_email, subject, body)
async def _send_email(to: str, subject: str, body: str) -> bool:
"""Send a plain-text email via SMTP.
Args:
to: Recipient email.
subject: Email subject.
body: Plain text body.
Returns:
True on success.
"""
if not cfg.SMTP_USERNAME or not cfg.SMTP_PASSWORD:
logger.warning("SMTP not configured β€” email not sent", to=to, subject=subject)
return False
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = f"{cfg.EMAIL_FROM_NAME} <{cfg.EMAIL_FROM_ADDRESS or cfg.SMTP_USERNAME}>"
msg["To"] = to
msg.attach(MIMEText(body, "plain"))
try:
context = ssl.create_default_context()
with smtplib.SMTP_SSL(cfg.SMTP_HOST, cfg.SMTP_PORT, context=context) as server:
server.login(str(cfg.SMTP_USERNAME), cfg.SMTP_PASSWORD)
server.sendmail(str(cfg.SMTP_USERNAME), to, msg.as_string())
logger.info("Email sent", to=to, subject=subject)
return True
except Exception as e:
logger.error("Failed to send email", to=to, error=str(e))
return False