import json from datetime import datetime, timezone from fastapi import APIRouter, HTTPException, Request, status from sqlmodel import select from app.db import SessionDep from app.models.mail_log import MailLog, MailLogStatus from app.services.postal_webhook import verify_signature router = APIRouter(prefix="/webhooks", tags=["webhooks"]) _STATUS_BY_EVENT = { "MessageSent": MailLogStatus.DELIVERED, "MessageDelayed": MailLogStatus.DELAYED, "MessageDeliveryFailed": MailLogStatus.FAILED, "MessageBounced": MailLogStatus.BOUNCED, "MessageHeld": MailLogStatus.HELD, } @router.post("/postal") async def postal_webhook(request: Request, session: SessionDep) -> dict: raw_body = await request.body() signature = request.headers.get("X-Postal-Signature-256") if not signature or not verify_signature(raw_body, signature): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid signature") payload = json.loads(raw_body) event = payload.get("event") event_payload = payload.get("payload", {}) message_info = event_payload.get("message") if not message_info or "id" not in message_info: return {"status": "ignored"} # fx DomainDNSError — intet at korrelere mod mail_log = session.exec( select(MailLog).where(MailLog.postal_message_id == message_info["id"]) ).first() if mail_log is None: return {"status": "ignored"} if event == "MessageLoaded": mail_log.opened_at = datetime.now(timezone.utc) elif event == "MessageLinkClicked": mail_log.clicked_at = datetime.now(timezone.utc) elif event in _STATUS_BY_EVENT: mail_log.status = _STATUS_BY_EVENT[event] if mail_log.status == MailLogStatus.FAILED: mail_log.error_message = event_payload.get("output") or event_payload.get("details") else: return {"status": "ignored"} session.add(mail_log) session.commit() return {"status": "ok"}