atriumchain-api / utils /email_service.py
Jainish1808's picture
Upload folder using huggingface_hub
6ca66cb verified
"""
Email Service for OTP Verification
Sends OTP emails using SMTP (Gmail)
"""
import smtplib
import random
import string
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta
from typing import Optional, Tuple
import logging
logger = logging.getLogger(__name__)
# SMTP Configuration
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
SMTP_EMAIL = "jigneshprajapati.igenerate@gmail.com"
SMTP_PASSWORD = "vtvgwntllsfjqykx" # NOTE: For Gmail, you MUST use an App Password!
# To generate an App Password:
# 1. Go to Google Account settings
# 2. Security > 2-Step Verification (enable if not already)
# 3. App passwords > Select app: Mail, Select device: Other (Custom name)
# 4. Generate and use that 16-character password here
def generate_otp(length: int = 6) -> str:
"""Generate a random 6-digit OTP"""
return ''.join(random.choices(string.digits, k=length))
def send_otp_email(to_email: str, otp: str, purpose: str = "login") -> Tuple[bool, bool]:
"""
Send OTP via email with fallback support
Args:
to_email: Recipient email address
otp: 6-digit OTP code
purpose: 'login' or 'registration'
Returns:
Tuple[bool, bool]: (success, needs_fallback)
- (True, False): Email sent successfully
- (False, True): Email failed due to network issue, show OTP in UI
- (False, False): Email failed due to other error
"""
try:
# Create message
msg = MIMEMultipart('alternative')
msg['From'] = SMTP_EMAIL
msg['To'] = to_email
msg['Subject'] = f"Your OTP for {purpose.capitalize()} - Real Estate Platform"
# HTML email body with AtriumChain brand theme - Clean, Professional Design
html_body = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Arial, sans-serif; background-color: #F5F6F8; margin: 0; padding: 0; }}
.wrapper {{ max-width: 600px; margin: 40px auto; }}
.container {{ background: #FFFFFF; border-radius: 24px; overflow: hidden; box-shadow: 0 4px 24px rgba(15, 16, 19, 0.08); border: 1px solid #E5E7EB; }}
.header {{ background: #0F1013; padding: 48px 40px; text-align: center; }}
.logo {{ font-size: 28px; font-weight: 800; color: #FFFFFF; letter-spacing: -0.5px; margin: 0; }}
.logo span {{ color: #BBD2FB; }}
.tagline {{ color: rgba(255,255,255,0.7); font-size: 14px; margin: 8px 0 0 0; font-weight: 400; }}
.content {{ padding: 48px 40px; }}
.greeting {{ color: #0F1013; font-size: 20px; font-weight: 600; margin: 0 0 16px 0; }}
.message {{ color: #6B7280; font-size: 15px; line-height: 1.7; margin: 0 0 32px 0; }}
.otp-container {{ background: #F5F6F8; border: 2px solid #E5E7EB; border-radius: 16px; padding: 32px; text-align: center; margin: 32px 0; }}
.otp-label {{ margin: 0 0 12px 0; color: #6B7280; font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px; font-weight: 600; }}
.otp-code {{ font-size: 44px; font-weight: 800; color: #0F1013; letter-spacing: 8px; margin: 0; font-family: 'SF Mono', 'Roboto Mono', monospace; }}
.otp-validity {{ margin: 16px 0 0 0; color: #6B7280; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 6px; }}
.otp-validity svg {{ width: 14px; height: 14px; }}
.security-box {{ background: #FEF3C7; border-left: 4px solid #F59E0B; border-radius: 0 12px 12px 0; padding: 20px 24px; margin: 32px 0; }}
.security-title {{ color: #92400E; font-weight: 700; font-size: 14px; margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px; }}
.security-title svg {{ width: 16px; height: 16px; }}
.security-list {{ color: #78350F; font-size: 13px; line-height: 1.8; margin: 0; padding-left: 20px; }}
.security-list li {{ margin: 4px 0; }}
.divider {{ height: 1px; background: #E5E7EB; margin: 32px 0; }}
.footer-text {{ color: #6B7280; font-size: 14px; line-height: 1.6; margin: 0; }}
.signature {{ color: #0F1013; font-weight: 600; margin: 16px 0 0 0; }}
.footer {{ background: #F9FAFB; padding: 32px 40px; text-align: center; border-top: 1px solid #E5E7EB; }}
.footer p {{ color: #9CA3AF; font-size: 12px; margin: 4px 0; line-height: 1.5; }}
.footer-links {{ margin-top: 16px; }}
.footer-link {{ color: #0F1013; text-decoration: none; font-size: 12px; font-weight: 500; }}
.footer-link:hover {{ text-decoration: underline; }}
.brand-badge {{ display: inline-flex; align-items: center; gap: 8px; background: #0F1013; color: #FFFFFF; padding: 8px 16px; border-radius: 100px; font-size: 12px; font-weight: 600; margin-top: 20px; }}
.brand-badge svg {{ width: 16px; height: 16px; }}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<div class="header">
<h1 class="logo">Atrium<span>Chain</span></h1>
<p class="tagline">Blockchain-Powered Real Estate Investment</p>
</div>
<div class="content">
<p class="greeting">Hello there!</p>
<p class="message">You've requested to <strong style="color: #0F1013;">{purpose}</strong> to your AtriumChain account. Use the verification code below to complete your authentication:</p>
<div class="otp-container">
<p class="otp-label">Your Verification Code</p>
<p class="otp-code">{otp}</p>
<p class="otp-validity">
<svg fill="none" viewBox="0 0 24 24" stroke="#6B7280"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Valid for 10 minutes
</p>
</div>
<div class="security-box">
<div class="security-title">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
Security Notice
</div>
<ul class="security-list">
<li>This code expires in <strong>10 minutes</strong></li>
<li>Never share this code with anyone</li>
<li>AtriumChain will never ask for your OTP</li>
<li>If you didn't request this, ignore this email</li>
</ul>
</div>
<div class="divider"></div>
<p class="footer-text">Need help? Contact our support team anytime. We're here to assist you with your property investment journey.</p>
<p class="signature">The AtriumChain Team</p>
<div style="text-align: center;">
<div class="brand-badge">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
Secured by XRP Blockchain
</div>
</div>
</div>
<div class="footer">
<p>This is an automated message from AtriumChain.</p>
<p>© 2024 AtriumChain. All rights reserved.</p>
<div class="footer-links">
<a href="#" class="footer-link">Privacy Policy</a> &nbsp;•&nbsp;
<a href="#" class="footer-link">Terms of Service</a> &nbsp;•&nbsp;
<a href="#" class="footer-link">Help Center</a>
</div>
</div>
</div>
</div>
</body>
</html>
"""
# Attach HTML body
msg.attach(MIMEText(html_body, 'html'))
# Connect to SMTP server and send email
logger.info(f"[EMAIL] Connecting to SMTP server: {SMTP_SERVER}:{SMTP_PORT}")
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls() # Secure the connection
logger.info(f"[EMAIL] Logging in as: {SMTP_EMAIL}")
server.login(SMTP_EMAIL, SMTP_PASSWORD)
logger.info(f"[EMAIL] Sending OTP to: {to_email}")
server.send_message(msg)
logger.info(f"[EMAIL] [SUCCESS] OTP email sent successfully to {to_email}")
return True, False # Success, no fallback needed
except OSError as e:
# Network unreachable - common on Hugging Face where port 587 is blocked
logger.error(f"[EMAIL] [ERROR] Network error (SMTP port may be blocked): {e}")
logger.warning("[EMAIL] [FALLBACK] Returning OTP in API response for UI display")
return False, True # Failed, but use fallback (show OTP in UI)
except smtplib.SMTPAuthenticationError as e:
logger.error(f"[EMAIL] [ERROR] SMTP Authentication failed: {e}")
logger.error("[EMAIL] [WARNING] IMPORTANT: For Gmail, you MUST use an App Password!")
logger.error("[EMAIL] Steps to generate App Password:")
logger.error("[EMAIL] 1. Go to https://myaccount.google.com/security")
logger.error("[EMAIL] 2. Enable 2-Step Verification if not already enabled")
logger.error("[EMAIL] 3. Go to App passwords section")
logger.error("[EMAIL] 4. Generate a new app password for 'Mail' application")
logger.error("[EMAIL] 5. Use that 16-character password in email_service.py")
return False, False # Failed, no fallback
except smtplib.SMTPException as e:
logger.error(f"[EMAIL] [ERROR] SMTP error: {e}")
return False, False # Failed, no fallback
except Exception as e:
logger.error(f"[EMAIL] [ERROR] Failed to send OTP email: {e}")
return False, False # Failed, no fallback
def get_otp_expiry() -> datetime:
"""Get OTP expiry time (10 minutes from now)"""
return datetime.utcnow() + timedelta(minutes=10)