""" Resend webhook handler — receives delivery events and inbound emails. NOT behind @role_required: Resend posts here unauthenticated. Verification uses RESEND_WEBHOOK_SECRET via the Resend SDK. """ import resend from quart import Blueprint, jsonify, request from .core import config, execute, utcnow_iso bp = Blueprint("webhooks", __name__, url_prefix="/webhooks") # Maps Resend event types to (column_to_update, timestamp_column) pairs. _EVENT_UPDATES: dict[str, tuple[str, str | None]] = { "email.delivered": ("delivered", "delivered_at"), "email.bounced": ("bounced", "bounced_at"), "email.opened": ("opened", "opened_at"), "email.clicked": ("clicked", "clicked_at"), "email.complained": ("complained", None), } @bp.route("/resend", methods=["POST"]) async def resend_webhook(): """Handle Resend webhook events (delivery tracking + inbound email).""" body = await request.get_data() # Verify signature when secret is configured if config.RESEND_WEBHOOK_SECRET: svix_id = request.headers.get("svix-id", "") svix_timestamp = request.headers.get("svix-timestamp", "") svix_signature = request.headers.get("svix-signature", "") try: wh = resend.Webhooks(config.RESEND_WEBHOOK_SECRET) wh.verify(body, { "svix-id": svix_id, "svix-timestamp": svix_timestamp, "svix-signature": svix_signature, }) except Exception: return jsonify({"error": "invalid signature"}), 401 payload = await request.get_json() if not payload: return jsonify({"error": "empty payload"}), 400 event_type = payload.get("type", "") data = payload.get("data", {}) if event_type in _EVENT_UPDATES: _handle_delivery_event(event_type, data) elif event_type == "email.received": await _handle_inbound(data) return jsonify({"ok": True}) async def _handle_delivery_event(event_type: str, data: dict) -> None: """Update email_log with delivery event (idempotent).""" email_id = data.get("email_id", "") if not email_id: return last_event, ts_col = _EVENT_UPDATES[event_type] now = utcnow_iso() if ts_col: await execute( f"UPDATE email_log SET last_event = ?, {ts_col} = ? WHERE resend_id = ?", (last_event, now, email_id), ) else: await execute( "UPDATE email_log SET last_event = ? WHERE resend_id = ?", (last_event, email_id), ) async def _handle_inbound(data: dict) -> None: """Store an inbound email (INSERT OR IGNORE on resend_id).""" resend_id = data.get("email_id", "") if not resend_id: return now = utcnow_iso() await execute( """INSERT OR IGNORE INTO inbound_emails (resend_id, message_id, in_reply_to, from_addr, to_addr, subject, text_body, html_body, received_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( resend_id, data.get("message_id", ""), data.get("in_reply_to", ""), data.get("from", ""), data.get("to", [""])[0] if isinstance(data.get("to"), list) else data.get("to", ""), data.get("subject", ""), data.get("text", ""), data.get("html", ""), now, ), )