Spaces:
Running
Running
File size: 8,817 Bytes
09801ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | """
Signup Confirmation Email Service
Uses Resend API to send branded DataVision confirmation emails
"""
import os
import httpx
import secrets
from typing import Optional
from datetime import datetime, timedelta
import logging
import json
from pathlib import Path
logger = logging.getLogger(__name__)
# Email configuration
RESEND_API_KEY = os.getenv("RESEND_API_KEY")
FROM_EMAIL = os.getenv("FROM_EMAIL", "insights@ai20insights.tech")
APP_URL = os.getenv("APP_URL", "https://killerkumar-ai-business-analyst.hf.space")
# Storage for pending confirmations
CONFIRMATIONS_DIR = Path(__file__).parent.parent / "storage" / "pending_confirmations"
CONFIRMATIONS_DIR.mkdir(parents=True, exist_ok=True)
async def send_confirmation_email(email: str, user_id: str) -> dict:
"""Send DataVision branded confirmation email using Resend API"""
if not RESEND_API_KEY:
error_msg = "RESEND_API_KEY not configured"
logger.error(error_msg)
return {"success": False, "error": error_msg}
# Generate confirmation token
token = secrets.token_urlsafe(32)
# Store pending confirmation
confirmation_data = {
"user_id": user_id,
"email": email,
"token": token,
"created_at": datetime.utcnow().isoformat(),
"expires_at": (datetime.utcnow() + timedelta(hours=24)).isoformat()
}
token_file = CONFIRMATIONS_DIR / f"{token}.json"
with open(token_file, 'w') as f:
json.dump(confirmation_data, f)
# Build confirmation URL - points to frontend which calls API
confirmation_url = f"{APP_URL}/auth/confirm?token={token}"
# HTML email template
html_content = f'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm Your DataVision Account</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f3f4f6;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f3f4f6; padding: 40px 20px;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.08);">
<!-- Header -->
<tr>
<td style="background: linear-gradient(135deg, #0F172A 0%, #1E293B 100%); padding: 40px 30px; text-align: center;">
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: 700;">
DataVision
</h1>
<p style="margin: 8px 0 0 0; color: #14B8A6; font-size: 14px; font-weight: 600;">
Autonomous Data Intelligence Platform
</p>
</td>
</tr>
<!-- Content -->
<tr>
<td style="padding: 40px 30px;">
<h2 style="margin: 0 0 16px 0; color: #0f172a; font-size: 22px; font-weight: 700;">
Welcome to DataVision! π
</h2>
<p style="margin: 0 0 24px 0; color: #475569; line-height: 1.7; font-size: 15px;">
Thank you for signing up. Click the button below to confirm your email address and get started with autonomous data intelligence.
</p>
<!-- Button -->
<table cellpadding="0" cellspacing="0" style="margin: 25px 0;">
<tr>
<td style="border-radius: 8px; background: linear-gradient(135deg, #14B8A6 0%, #0D9488 100%);">
<a href="{confirmation_url}"
style="display: inline-block; padding: 14px 32px; color: #ffffff; text-decoration: none; font-weight: 600; font-size: 15px;">
Confirm Your Email β
</a>
</td>
</tr>
</table>
<p style="margin: 24px 0 0 0; color: #64748b; font-size: 13px; line-height: 1.6;">
This link will expire in 24 hours. If you didn't create an account with DataVision, you can safely ignore this email.
</p>
<div style="height: 1px; background: linear-gradient(to right, transparent, #e2e8f0, transparent); margin: 30px 0;"></div>
<p style="margin: 0; color: #94a3b8; font-size: 12px;">
If the button doesn't work, copy and paste this URL into your browser:<br>
<a href="{confirmation_url}" style="color: #14B8A6; word-break: break-all;">{confirmation_url}</a>
</p>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="padding: 30px; background: #f8fafc; border-top: 1px solid #e2e8f0; text-align: center;">
<p style="margin: 0; color: #64748b; font-size: 13px;">
Β© 2024 DataVision β Autonomous Data Intelligence Platform
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
'''
try:
async with httpx.AsyncClient(timeout=30.0) as client:
payload = {
"from": f"DataVision <{FROM_EMAIL}>",
"to": [email],
"subject": "Confirm Your DataVision Account",
"html": html_content,
"text": f"Welcome to DataVision!\n\nClick this link to confirm your email: {confirmation_url}\n\nThis link expires in 24 hours.",
}
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"π§ Confirmation email response: {response.status_code}")
if response.status_code != 200:
error_detail = response.text
logger.error(f"π§ Resend API error: {error_detail}")
return {"success": False, "error": error_detail}
result = response.json()
logger.info(f"β
Confirmation email sent to {email}! ID: {result.get('id', 'unknown')}")
return {"success": True, "email_id": result.get('id')}
except Exception as e:
logger.error(f"β Failed to send confirmation email: {str(e)}")
return {"success": False, "error": str(e)}
async def verify_confirmation_token(token: str) -> dict:
"""Verify a confirmation token and return user info"""
token_file = CONFIRMATIONS_DIR / f"{token}.json"
if not token_file.exists():
return {"success": False, "error": "Invalid or expired token"}
try:
with open(token_file, 'r') as f:
data = json.load(f)
# Check expiration
expires_at = datetime.fromisoformat(data["expires_at"])
if datetime.utcnow() > expires_at:
token_file.unlink() # Delete expired token
return {"success": False, "error": "Token has expired"}
# Token is valid - delete it (one-time use)
token_file.unlink()
return {
"success": True,
"user_id": data["user_id"],
"email": data["email"]
}
except Exception as e:
logger.error(f"Error verifying token: {e}")
return {"success": False, "error": str(e)}
|