#!/usr/bin/env python3 """ 🚨 LIVE EMAIL DELIVERY VERIFICATION SCRIPT 🚨 This script performs a "Live Fire" test to verify that emails actually leave our infrastructure via SendGrid and arrive at an external, independent mailbox. Steps: 1. Temporarily configure SendGrid API key (from environment or manual input) 2. Generate unique test email address using Mail7.io disposable email 3. Send a real registration/test email using backend's EmailService 4. Wait for email propagation (10-20 seconds) 5. Query Mail7.io API to verify email arrival 6. Log full headers as proof of delivery """ import os import sys import time import uuid import json import logging import asyncio import httpx from datetime import datetime from typing import Dict, Any, Optional # Add backend to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("fire_drill_email") # Mail7.io Configuration MAIL7_API_BASE = "https://api.mail7.io" # Note: Mail7.io requires API key and secret for inbox access # These should be obtained from mail7.io dashboard MAIL7_API_KEY = os.getenv("MAIL7_API_KEY", "") MAIL7_API_SECRET = os.getenv("MAIL7_API_SECRET", "") class LiveEmailVerification: def __init__(self): self.test_id = str(uuid.uuid4())[:8] self.test_email = f"sf-audit-{self.test_id}@mail7.io" self.security_token = f"SF-VERIFY-{self.test_id}-{int(time.time())}" self.results = {} async def check_mail7_config(self) -> bool: """Check if Mail7.io API credentials are configured.""" if not MAIL7_API_KEY or not MAIL7_API_SECRET: logger.error("Mail7.io API credentials not configured!") logger.error("Set MAIL7_API_KEY and MAIL7_API_SECRET environment variables") logger.error("Get credentials from: https://mail7.io/dashboard") return False return True async def send_test_email(self) -> bool: """ Send a test email using the backend's EmailService. This requires temporarily configuring SendGrid or production SMTP. """ try: # Import email service components from app.services.email_manager import EmailManager from app.db.session import AsyncSessionLocal logger.info(f"Sending test email to: {self.test_email}") logger.info(f"Security token in body: {self.security_token}") # Create test variables for email template variables = { "first_name": "FireDrill", "link": f"https://servicefinder.hu/verify?token={self.security_token}", "token": self.security_token, "test_id": self.test_id, "timestamp": datetime.utcnow().isoformat() } # Send email using verification template async with AsyncSessionLocal() as db: result = await EmailManager.send_email( recipient=self.test_email, template_key="verification", variables=variables, lang="en", db=db ) if result: logger.info(f"Email sent successfully: {result}") self.results["send_result"] = result return True else: logger.error("Email sending failed or returned None") return False except Exception as e: logger.error(f"Error sending test email: {e}", exc_info=True) return False async def check_mail7_inbox(self) -> Optional[Dict[str, Any]]: """ Query Mail7.io API to check for incoming email. Returns email data if found, None otherwise. """ if not await self.check_mail7_config(): return None try: # Mail7.io inbox API endpoint params = { "apikey": MAIL7_API_KEY, "apisecret": MAIL7_API_SECRET, "to": self.test_email } async with httpx.AsyncClient(timeout=30.0) as client: response = await client.get( f"{MAIL7_API_BASE}/inbox", params=params ) if response.status_code == 200: data = response.json() logger.info(f"Mail7 API response: {json.dumps(data, indent=2)}") if data.get("data"): emails = data["data"] logger.info(f"Found {len(emails)} email(s) in inbox") # Look for our security token in email content for email in emails: subject = email.get("subject", "") text = email.get("text", "") html = email.get("html", "") # Check if our security token is in the email if (self.security_token in subject or self.security_token in text or self.security_token in html): logger.info(f"Found matching email with ID: {email.get('id')}") self.results["received_email"] = email return email logger.warning("Email found but security token not matched") self.results["received_email"] = emails[0] if emails else None return emails[0] if emails else None else: logger.info("No emails found in inbox yet") return None else: logger.error(f"Mail7 API error: {response.status_code} - {response.text}") return None except Exception as e: logger.error(f"Error checking Mail7 inbox: {e}", exc_info=True) return None async def wait_and_verify(self, max_attempts: int = 6, delay: int = 10) -> bool: """ Wait for email to arrive and verify it. Returns True if verification successful. """ logger.info(f"Waiting for email to arrive (checking every {delay} seconds)...") for attempt in range(1, max_attempts + 1): logger.info(f"Attempt {attempt}/{max_attempts}...") email_data = await self.check_mail7_inbox() if email_data: logger.info("āœ… EMAIL VERIFIED - Successfully received at external mailbox!") # Log full headers headers = email_data.get("headers", {}) logger.info(f"Email headers: {json.dumps(headers, indent=2)}") # Save proof of delivery self.save_proof_of_delivery(email_data) return True if attempt < max_attempts: logger.info(f"Waiting {delay} seconds before next check...") await asyncio.sleep(delay) logger.error("āŒ EMAIL NOT RECEIVED - Failed to verify delivery") return False def save_proof_of_delivery(self, email_data: Dict[str, Any]): """Save proof of delivery to log file.""" log_dir = "docs/v201/testing_logs" os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, "live_email_success.log") proof = { "timestamp": datetime.utcnow().isoformat(), "test_id": self.test_id, "test_email": self.test_email, "security_token": self.security_token, "verification_status": "SUCCESS", "email_data": { "id": email_data.get("id"), "from": email_data.get("from"), "to": email_data.get("to"), "subject": email_data.get("subject"), "received_at": email_data.get("received"), "headers": email_data.get("headers", {}) }, "full_response": email_data } with open(log_file, "a") as f: f.write("\n" + "="*80 + "\n") f.write(f"LIVE EMAIL VERIFICATION SUCCESS - {datetime.utcnow()}\n") f.write("="*80 + "\n") f.write(json.dumps(proof, indent=2)) f.write("\n") logger.info(f"Proof of delivery saved to: {log_file}") def print_summary(self): """Print test summary.""" print("\n" + "="*80) print("LIVE EMAIL FIRE DRILL - TEST SUMMARY") print("="*80) print(f"Test ID: {self.test_id}") print(f"Test Email: {self.test_email}") print(f"Security Token: {self.security_token}") print(f"Timestamp: {datetime.utcnow()}") print(f"Mail7 API Configured: {bool(MAIL7_API_KEY and MAIL7_API_SECRET)}") if "send_result" in self.results: print(f"Send Result: {self.results['send_result']}") if "received_email" in self.results: email = self.results["received_email"] print(f"Email Received: YES") print(f" From: {email.get('from')}") print(f" Subject: {email.get('subject')}") print(f" Received: {email.get('received')}") else: print(f"Email Received: NO") print("="*80) async def main(): """Main execution function.""" print("\nšŸš€ STARTING LIVE EMAIL DELIVERY VERIFICATION") print("="*60) # Check environment configuration sendgrid_key = os.getenv("SENDGRID_API_KEY") if not sendgrid_key: print("āš ļø WARNING: SENDGRID_API_KEY not set in environment") print("The test will use current email configuration (likely Mailpit)") print("For true production test, set SENDGRID_API_KEY environment variable") print("="*60) # Create verifier verifier = LiveEmailVerification() print(f"Test email address: {verifier.test_email}") print(f"Security token: {verifier.security_token}") print("="*60) # Step 1: Send test email print("\nšŸ“§ STEP 1: Sending test email...") send_success = await verifier.send_test_email() if not send_success: print("āŒ Failed to send test email. Aborting.") return False print("āœ… Test email sent successfully") # Step 2: Wait and verify delivery print("\nā³ STEP 2: Waiting for email delivery verification...") verification_success = await verifier.wait_and_verify() # Print summary verifier.print_summary() if verification_success: print("\nšŸŽ‰ SUCCESS: Live email delivery verified!") print("Email successfully left our infrastructure and arrived at external mailbox.") return True else: print("\nāŒ FAILURE: Email delivery not verified") print("Possible issues:") print("1. SendGrid not properly configured") print("2. Mail7.io API credentials incorrect") print("3. Email stuck in queue or blocked") print("4. Network/DNS issues") return False if __name__ == "__main__": # Run async main success = asyncio.run(main()) # Exit with appropriate code sys.exit(0 if success else 1)