Files
service-finder/tests/fire_drill_email.py

305 lines
11 KiB
Python

#!/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)