"""Author RAG — Email Service Extensions. Adds email templates required by Phase 1 billing flows: - send_welcome: fired on account activation (POST /api/auth/activate) - send_password_reset: fired by forgot-password flow - send_payment_failed: dunning email (fired by Stripe webhook) - send_subscription_cancelled: cancellation confirmation - send_token_warning: author approaching budget limit (80% / 95%) All methods are thin wrappers around _send() — the existing SMTP infrastructure. """ def _extend_email_service(): """Monkey-patch new methods onto EmailService if they don't exist yet.""" from app.services.email_service import EmailService from app.config import get_settings cfg = get_settings() if not hasattr(EmailService, "send_password_reset"): def send_password_reset(self, to_email: str, reset_url: str) -> None: """Send HMAC-signed password reset link.""" self._send( to=to_email, subject="Reset your ChatBot AI password", body=( f"Hi,\n\n" f"Click the link below to reset your password. " f"This link expires in 1 hour.\n\n" f"{reset_url}\n\n" f"If you didn't request a password reset, you can ignore this email.\n\n" f"— The ChatBot AI Team" ), ) EmailService.send_password_reset = send_password_reset if not hasattr(EmailService, "send_payment_failed"): def send_payment_failed(self, to_email: str, author_name: str, grace_days: int = 7) -> None: """Dunning email — payment failed, grace period active.""" self._send( to=to_email, subject="⚠️ Payment failed — action required", body=( f"Hi {author_name},\n\n" f"We couldn't process your subscription payment. " f"Your chatbot will continue working for {grace_days} more days.\n\n" f"Please update your payment method to avoid interruption:\n" f"{cfg.FRONTEND_URL}/dashboard/billing\n\n" f"Questions? Reply to this email.\n\n" f"— The ChatBot AI Team" ), ) EmailService.send_payment_failed = send_payment_failed if not hasattr(EmailService, "send_subscription_cancelled"): def send_subscription_cancelled(self, to_email: str, author_name: str, access_until: str) -> None: """Cancellation confirmation — access continues to period end.""" self._send( to=to_email, subject="Your subscription has been cancelled", body=( f"Hi {author_name},\n\n" f"Your ChatBot AI subscription has been cancelled. " f"You have access until {access_until}.\n\n" f"We'd love to know why you left — reply to this email with feedback.\n\n" f"You can reactivate any time at: {cfg.FRONTEND_URL}/#pricing\n\n" f"— The ChatBot AI Team" ), ) EmailService.send_subscription_cancelled = send_subscription_cancelled if not hasattr(EmailService, "send_token_warning"): def send_token_warning(self, to_email: str, author_name: str, pct_used: int) -> None: """Proactive token budget warning (80% or 95%).""" threshold = "almost" if pct_used >= 95 else "80%" self._send( to=to_email, subject=f"⚡ You've used {pct_used}% of your monthly AI budget", body=( f"Hi {author_name},\n\n" f"You've used {pct_used}% of your monthly token budget. " f"{'Your chatbot may stop responding soon.' if pct_used >= 95 else 'Heads up — you may want to upgrade before month end.'}\n\n" f"Upgrade your plan: {cfg.FRONTEND_URL}/#pricing\n\n" f"Or manage your billing: {cfg.FRONTEND_URL}/dashboard/billing\n\n" f"— The ChatBot AI Team" ), ) EmailService.send_token_warning = send_token_warning # Auto-extend on import _extend_email_service()