| 202 | |
| 203 | |
| 204 | def send_email(to: EmailStr, subject: str, body: str): |
| 205 | from fastapi import HTTPException |
| 206 | |
| 207 | lh_config = get_learnhouse_config() |
| 208 | mailing = lh_config.mailing_config |
| 209 | sender = f"LearnHouse <{mailing.system_email_address}>" |
| 210 | |
| 211 | # Resend (and most providers) require a plain `email@example.com` string. |
| 212 | # Pydantic's EmailStr is a str subclass, but third-party JSON serializers |
| 213 | # can mis-handle it — coerce to a stripped plain str and validate shape |
| 214 | # so a malformed stored email surfaces here rather than as an opaque |
| 215 | # provider 4xx. |
| 216 | to_addr = str(to).strip() |
| 217 | if not to_addr or "@" not in to_addr: |
| 218 | logger.error("Refusing to send email: invalid recipient %r", to) |
| 219 | raise HTTPException(status_code=400, detail="Invalid recipient email address") |
| 220 | |
| 221 | if mailing.email_provider == "smtp": |
| 222 | return _send_email_smtp(sender, to_addr, subject, body, mailing) |
| 223 | else: |
| 224 | return _send_email_resend(sender, to_addr, subject, body, mailing) |
| 225 | |
| 226 | |
| 227 | def _send_email_resend(sender: str, to: str, subject: str, body: str, mailing): |