"""Email service for sending exam reports to teachers."""
from __future__ import annotations
import os
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Optional
import markdown
from .config import get_settings
def markdown_to_html(md_content: str) -> str:
"""Convert markdown to styled HTML for email."""
# Convert markdown to HTML
html_body = markdown.markdown(
md_content,
extensions=['tables', 'fenced_code', 'nl2br']
)
# Wrap in email template
return f"""
{html_body}
"""
async def send_email_via_gmail(
to_email: str,
subject: str,
body_markdown: str,
gmail_user: str,
gmail_app_password: str
) -> dict:
"""
Send email using Gmail SMTP.
Requires:
- Gmail account
- App Password (not your regular password)
To get an App Password:
1. Enable 2-Step Verification on your Google account
2. Go to https://myaccount.google.com/apppasswords
3. Generate an app password for "Mail"
"""
try:
html_content = markdown_to_html(body_markdown)
# Create message
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = gmail_user
message["To"] = to_email
# Add plain text and HTML versions
part1 = MIMEText(body_markdown, "plain")
part2 = MIMEText(html_content, "html")
message.attach(part1)
message.attach(part2)
# Create secure connection and send
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
server.login(gmail_user, gmail_app_password)
server.sendmail(gmail_user, to_email, message.as_string())
return {"status": "ok", "message": "Email sent via Gmail"}
except smtplib.SMTPAuthenticationError:
return {
"status": "error",
"message": "Gmail authentication failed. Check your email and app password."
}
except Exception as e:
return {
"status": "error",
"message": f"Failed to send email: {str(e)}"
}
async def send_email_via_sendgrid(
to_email: str,
subject: str,
body_markdown: str
) -> dict:
"""Send email using SendGrid API."""
settings = get_settings()
try:
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Email, To, Content, HtmlContent
html_content = markdown_to_html(body_markdown)
message = Mail(
from_email=Email(settings.sendgrid_from_email, "ClassLens"),
to_emails=To(to_email),
subject=subject,
html_content=HtmlContent(html_content)
)
message.add_content(Content("text/plain", body_markdown))
sg = SendGridAPIClient(settings.sendgrid_api_key)
response = sg.send(message)
if response.status_code in (200, 201, 202):
return {"status": "ok", "message": "Email sent via SendGrid"}
else:
return {
"status": "error",
"message": f"SendGrid returned status {response.status_code}"
}
except Exception as e:
return {
"status": "error",
"message": str(e)
}
async def send_email_report(
email: str,
subject: str,
body_markdown: str
) -> dict:
"""
Send an email report to a teacher.
Tries in order:
1. Gmail SMTP (if configured)
2. SendGrid (if configured)
3. Logs to console (fallback)
Returns:
{"status": "ok"} on success
{"status": "error", "message": str} on failure
"""
# Try Gmail SMTP first
gmail_user = os.getenv("GMAIL_USER", "")
gmail_app_password = os.getenv("GMAIL_APP_PASSWORD", "")
if gmail_user and gmail_app_password:
result = await send_email_via_gmail(email, subject, body_markdown, gmail_user, gmail_app_password)
if result["status"] == "ok":
print(f"📧 Email sent to {email} via Gmail")
return result
else:
print(f"⚠️ Gmail failed: {result['message']}, trying fallback...")
# Try SendGrid
settings = get_settings()
if settings.sendgrid_api_key:
result = await send_email_via_sendgrid(email, subject, body_markdown)
if result["status"] == "ok":
print(f"📧 Email sent to {email} via SendGrid")
return result
else:
print(f"⚠️ SendGrid failed: {result['message']}")
# Fallback: print to console
print(f"\n{'='*60}")
print(f"📧 EMAIL (not sent - no email service configured)")
print(f"To: {email}")
print(f"Subject: {subject}")
print(f"{'='*60}")
print(body_markdown[:500] + "..." if len(body_markdown) > 500 else body_markdown)
print(f"{'='*60}\n")
return {
"status": "ok",
"message": "Email logged to console (configure GMAIL_USER/GMAIL_APP_PASSWORD or SENDGRID_API_KEY to send real emails)"
}