Mailglass.Webhook.Provider behaviour (Mailglass v0.1.0)

Copy Markdown View Source

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

normalize(raw_body, headers)

@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).

verify!(raw_body, headers, config)

@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.