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