""" Email Service - Send notification emails Uses Resend API (can be swapped for SendGrid, AWS SES, etc.) """ import os import httpx from typing import Optional import logging logger = logging.getLogger(__name__) # Email configuration RESEND_API_KEY = os.getenv("RESEND_API_KEY") # FROM_EMAIL must be from a verified domain in Resend # User has verified: ai20insights.tech FROM_EMAIL = os.getenv("FROM_EMAIL", "insights@ai20insights.tech") APP_URL = os.getenv("APP_URL", "https://datavision-ai-datavision.hf.space") async def send_insight_email( to_email: str, title: str, body: str, chart_payload: Optional[dict] = None, workspace_id: str = None ): """Send insight notification email via Resend API or SMTP fallback""" html_content = render_insight_email_template(title, body, chart_payload, workspace_id) # 1. Try Resend API first if RESEND_API_KEY is configured if RESEND_API_KEY: logger.info(f"📧 Sending email via Resend API from {FROM_EMAIL} to {to_email}") try: async with httpx.AsyncClient(timeout=15.0) as client: payload = { "from": f"DataVision <{FROM_EMAIL}>", "to": [to_email], "subject": f"📊 DataVision: {title}", "html": html_content, "text": render_plain_text_email(title, body), } response = await client.post( "https://api.resend.com/emails", headers={ "Authorization": f"Bearer {RESEND_API_KEY}", "Content-Type": "application/json" }, json=payload ) if response.status_code == 200: result = response.json() logger.info(f"✅ Email sent via Resend API! ID: {result.get('id', 'unknown')}") return result else: logger.warning(f"📧 Resend API status {response.status_code}: {response.text}") except Exception as e: logger.warning(f"📧 Resend API attempt failed: {e}") # 2. Try SMTP fallback if SMTP env vars are present smtp_host = os.getenv("SMTP_HOST") smtp_user = os.getenv("SMTP_USER") smtp_pass = os.getenv("SMTP_PASSWORD") smtp_port = int(os.getenv("SMTP_PORT", "587")) if smtp_host and smtp_user and smtp_pass: logger.info(f"📧 Attempting SMTP send via {smtp_host}:{smtp_port} to {to_email}") try: import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart msg = MIMEMultipart("alternative") msg["Subject"] = f"📊 DataVision: {title}" msg["From"] = f"DataVision <{smtp_user}>" msg["To"] = to_email part1 = MIMEText(render_plain_text_email(title, body), "plain") part2 = MIMEText(html_content, "html") msg.attach(part1) msg.attach(part2) with smtplib.SMTP(smtp_host, smtp_port, timeout=10) as server: server.starttls() server.login(smtp_user, smtp_pass) server.sendmail(smtp_user, [to_email], msg.as_string()) logger.info(f"✅ Email sent via SMTP to {to_email}!") return {"status": "sent", "provider": "smtp"} except Exception as smtp_err: logger.error(f"❌ SMTP send failed: {smtp_err}") logger.warning("📧 No active email provider (Resend API key or SMTP config). Simulating email send for development/testing.") # Log the email content to console so developers can still see what would have been sent logger.info(f"[SIMULATED EMAIL TO {to_email}]: {title}\n{body[:100]}...") return {"status": "simulated", "provider": "none"} def render_insight_email_template( title: str, body: str, chart_payload: Optional[dict], workspace_id: str ) -> str: """Render ultra-premium HTML email template for insight notification (DataVision Enterprise)""" # Modern Enterprise Gradient (Teal/Emerald) button_gradient = "linear-gradient(135deg, #0d9488 0%, #059669 100%)" # Check if we should link to a specific chat context or general dashboard cta_text = "View Dashboard Insights" if not chart_payload: cta_text = "Discuss with AI Analyst" return f''' {title}
✨ AI Intelligence Digest

{title}

Powered by Datavision AI.

{body.replace(chr(10), '
')}
{cta_text} →

DataVision

This report was autonomously generated by your DataVision agents.
If you wish to change your notification preferences, you can do so in your account settings.

© 2026 DataVision AI Analytics. All rights reserved.

''' # Alternative: Plain text version for email clients that don't support HTML def render_plain_text_email(title: str, body: str) -> str: """Render plain text version of email""" return f""" AI INSIGHT DETECTED ================== {title} {body} View in dashboard: {APP_URL}/dashboard --- DataVision - Universal Data Intelligence Manage your notification preferences: {APP_URL}/settings/notifications """ async def send_password_reset_email(to_email: str, reset_link: str) -> dict: """Send a branded password reset email""" if not RESEND_API_KEY: error_msg = "Email service not configured. Please add RESEND_API_KEY to your .env file." logger.warning(error_msg) raise Exception(error_msg) html_content = render_password_reset_template(reset_link) plain_text = f""" DataVision Password Reset ======================== We received a request to reset your password for your DataVision account. Click the link below to reset your password: {reset_link} This link will expire in 1 hour. If you didn't request this, you can safely ignore this email. --- DataVision AI Analytics {APP_URL} """ logger.info(f"📧 Sending password reset email to {to_email}") try: async with httpx.AsyncClient(timeout=30.0) as client: payload = { "from": f"DataVision <{FROM_EMAIL}>", "to": [to_email], "subject": "🔐 Reset Your DataVision Password", "html": html_content, "text": plain_text, } response = await client.post( "https://api.resend.com/emails", headers={ "Authorization": f"Bearer {RESEND_API_KEY}", "Content-Type": "application/json" }, json=payload ) logger.info(f"📧 Resend response status: {response.status_code}") if response.status_code != 200: error_detail = response.text logger.error(f"📧 Resend API error: {error_detail}") raise Exception(f"Resend API error ({response.status_code}): {error_detail}") result = response.json() logger.info(f"✅ Password reset email sent! ID: {result.get('id', 'unknown')}") return result except httpx.TimeoutException: error_msg = "Email request timed out" logger.error(error_msg) raise Exception(error_msg) except Exception as e: logger.error(f"❌ Failed to send password reset email: {str(e)}") raise def render_password_reset_template(reset_link: str) -> str: """Render branded HTML password reset email template""" return f''' Reset Your Password - DataVision
🔐 Password Reset

DataVision

Reset Your Password

Hi there! 👋

We received a request to reset your password for your DataVision account.

Click the button below to create a new password:

🔑 Reset Password

⏰ This link expires in 1 hour.

If you didn't request this password reset, you can safely ignore this email. Your password will remain unchanged.

If the button doesn't work, copy and paste this link into your browser:
{reset_link}

© 2026 DataVision AI. All rights reserved.

DashboardSettings

'''