Mailglass.Webhook.Plug (Mailglass v0.1.0)

Copy Markdown View Source

Single-ingress webhook orchestrator (CONTEXT D-10).

Plugged at adopter-mounted paths via Mailglass.Webhook.Router (Plan 05). Owns the full request lifecycle:

  1. Extract raw_body from conn.private[:raw_body] (populated by Mailglass.Webhook.CachingBodyReader in the adopter's Plug.Parsers :body_reader)
  2. Dispatch to Mailglass.Webhook.Provider impl per route opts (provider: :postmark | :sendgrid)
  3. Provider.verify!/3 — raises %SignatureError{} on failure
  4. Mailglass.Tenancy.resolve_webhook_tenant/1 (D-12) — runs AFTER verify (D-13)
  5. Mailglass.Tenancy.with_tenant/2 BLOCK form — clean tenant cleanup on raise (Pitfall 7)
  6. Provider.normalize/2 — pure, returns [%Event{}]
  7. Mailglass.Webhook.Ingest.ingest_multi/3 — single Ecto.Multi inside Repo.transact/1 (Plan 06, forward-declared)
  8. Post-commit: Mailglass.Outbound.Projector.broadcast_delivery_updated/3 per matched delivery (Phase 3 D-04 — Plan 06 returns the events_with_deliveries 3-tuples for this loop)
  9. send_resp(conn, 200, "")

Response code matrix (CONTEXT D-10 + D-14 + D-21)

OutcomeStatusNotes
Success200Normal happy path
Duplicate replay (UNIQUE collision)200Idempotent — provider sees no error
%SignatureError{} (any of 7+ atoms)401Logger.warning with provider + atom
%TenancyError{:webhook_tenant_unresolved}422Distinct from signature failure
%ConfigError{:webhook_caching_body_reader_missing}500Adopter wiring gap
%ConfigError{:webhook_verification_key_missing}500Missing provider secret
Ingest {:error, reason}500Logger.error with reason atom only

Telemetry (CONTEXT D-22)

Emits [:mailglass, :webhook, :ingest, :start | :stop | :exception] around the entire call/2 body via Mailglass.Webhook.Telemetry.ingest_span/2 (Plan 08). Stop metadata follows D-23 whitelist: %{provider, tenant_id, status, event_count, duplicate, failure_reason} — never IP, headers, or payload bytes.

Also emits [:mailglass, :webhook, :signature, :verify, :start | :stop | :exception] around Provider.verify!/3 via Mailglass.Webhook.Telemetry.verify_span/2.

Failure log discipline (CONTEXT D-24)

Logger.warning on signature failure includes provider + atom reason only. Never the source IP, headers, or payload excerpts. Adopters wanting IP-based abuse triage attach their own telemetry handler on [:mailglass, :webhook, :signature, :verify, :stop] with status: :failed and pull conn.remote_ip from their own plug lineage.

Forward-declared contracts

Mailglass.Webhook.Ingest.ingest_multi/3 is shipped by Plan 06 (Wave 3). This module references it directly; the @compile {:no_warn_undefined, ...} attribute below suppresses the warning during mix compile --warnings-as-errors until Plan 06 lands.