"""Public contact form endpoint — no authentication required.""" from __future__ import annotations import html import logging import time from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from fastapi import APIRouter, Request from pydantic import BaseModel, EmailStr, Field from backend.config import settings from backend.services.email import _send_raw logger = logging.getLogger(__name__) router = APIRouter() # Simple in-memory rate limiting (per-instance, resets on deploy) _recent: dict[str, float] = {} _RATE_LIMIT_SECONDS = 60 class ContactRequest(BaseModel): name: str = Field(..., min_length=1, max_length=200) email: EmailStr = Field(..., max_length=254) message: str = Field(..., min_length=1, max_length=5000) company: str = Field("", max_length=200) subject: str = Field("", max_length=200) honeypot: str = Field("", alias="_honey") class ContactResponse(BaseModel): success: bool message: str def _build_contact_message(data: ContactRequest) -> MIMEMultipart: """Build the contact form email.""" msg = MIMEMultipart("alternative") msg["From"] = f"Periscope <{settings.email_sender}>" msg["To"] = settings.contact_recipient msg["Reply-To"] = data.email msg["Subject"] = f"[Periscope Contact] {data.subject or 'New message'} from {data.name}" # Plain text lines = [ f"Name: {data.name}", f"Email: {data.email}", ] if data.company: lines.append(f"Company: {data.company}") if data.subject: lines.append(f"Subject: {data.subject}") lines += ["", data.message, "", "— Sent from the Periscope contact form"] msg.attach(MIMEText("\n".join(lines), "plain")) # HTML name = html.escape(data.name) email = html.escape(data.email) company = html.escape(data.company) subject = html.escape(data.subject) message = html.escape(data.message) rows = f"""\
Sent from the Periscope contact form