Behaviour for webhook providers (SendGrid, Postmark, etc).
Summary
Callbacks
Normalize a verified webhook body into a list of %Mailglass.Events.Event{}
structs in the Anymail taxonomy verbatim (PROJECT D-14 + amendment).
Verify a webhook request's authenticity. Receives a 3-tuple of
(raw_body, headers, config) — NOT a %Plug.Conn{} — per CONTEXT D-02
so the contract is portable to v0.5 inbound + SES SQS polling contexts.
Callbacks
@callback normalize( raw_body :: binary(), headers :: [{String.t(), String.t()}] ) :: [Mailglass.Events.Event.t()]
Normalize a verified webhook body into a list of %Mailglass.Events.Event{}
structs in the Anymail taxonomy verbatim (PROJECT D-14 + amendment).
Pure — no DB, no PubSub, no telemetry. Exhaustive case per provider's
documented event types; unmapped types fall through to :unknown with
Logger.warning (NEVER silent _ -> :hard_bounce; per D-05).
Provider identifiers ("provider", "provider_event_id", "record_type",
"message_id") are stashed in Event.metadata with STRING keys per
revision W9 — JSONB roundtrip safety; Plan 04's Ingest reads them from
metadata to populate the mailglass_webhook_events row. The ledger's
%Event{} struct itself has no :provider column (per V02 schema —
provider identity lives on mailglass_webhook_events).
@callback verify!( raw_body :: binary(), headers :: [{String.t(), String.t()}], config :: map() ) :: :ok
Verify a webhook request's authenticity. Receives a 3-tuple of
(raw_body, headers, config) — NOT a %Plug.Conn{} — per CONTEXT D-02
so the contract is portable to v0.5 inbound + SES SQS polling contexts.
Returns :ok on success. Raises %Mailglass.SignatureError{} with
one of the seven closed atoms (per D-21) on failure. Raises
%Mailglass.ConfigError{type: :webhook_verification_key_missing} on
missing per-tenant secret.