# `SquatchMail.SNS.Processor`
[🔗](https://github.com/Axio-Intelligence/Squatch-Mail/blob/v0.1.0/lib/squatch_mail/sns/processor.ex#L1)

Orchestrates inbound SNS webhook delivery: token auth, signature
verification, SES event normalization, persistence, and suppression rules.

This is the single entry point the webhook controller calls with the raw
request body and the `:token` path parameter. Every inbound payload is
logged to `webhook_logs` (via `SquatchMail.Tracker.log_webhook/1`)
regardless of outcome, so ingestion failures are always inspectable.

## SES event schema families

SES publishes events in two shapes depending on how the host configured
notifications:

  * **Event publishing** (configuration sets) - top-level `"eventType"`,
    e.g. `"Bounce"`, `"Send"`, `"Open"`.
  * **Legacy notifications** (identity-level feedback forwarding) -
    top-level `"notificationType"`, limited to `"Bounce"`, `"Complaint"`,
    `"Delivery"`.

Both are normalized to the same lowercase `event_type` vocabulary before
reaching `SquatchMail.Tracker.record_event/1`.

## Idempotency

SNS is at-least-once delivery and retries on non-2xx responses, so the same
SES event can arrive more than once. Events are deduped on
`(message_id, event_type, recipient, occurred_at)` via an application-level
existence check immediately before insert. This is a check-then-insert, not
a database constraint, so there is a race window between two concurrent
deliveries of the same retried event; a unique index would close it fully
but requires a migration version bump, which is out of scope here. Given
SNS retries are seconds-to-minutes apart in practice (not concurrent), this
tradeoff is acceptable for now - noted so a future migration can add the
constraint.

# `outcome`

```elixir
@type outcome() :: :processed | :ignored
```

# `reason`

```elixir
@type reason() :: :invalid_token | :invalid_json | :signature_invalid | term()
```

# `process`

```elixir
@spec process(String.t(), String.t()) :: {:ok, outcome()} | {:error, reason()}
```

Processes a raw inbound webhook request body for the source identified by
`token` (the path token, `SquatchMail.Source.webhook_token`).

Returns `{:ok, :processed}` for messages that resulted in stored events,
confirmed subscriptions, etc; `{:ok, :ignored}` for messages intentionally
not acted on (e.g. `UnsubscribeConfirmation`, unknown event types);
`{:error, reason}` otherwise. Every call logs to `webhook_logs`, including
on error paths that occur before a `Source` can even be resolved.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
