Spaces:
Running
Running
| """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 | |