# `MailglassInbound.SignatureError`
[🔗](https://github.com/szTheory/mailglass/blob/v2.3.0/lib/mailglass_inbound/signature_error.ex#L1)

Structured, **no-recovery** error for inbound provider signature failures
(Mailgun HMAC, AWS SES SNS X.509, and SNS `SubscribeURL` trust-policy checks).

Webhook signature errors are never retryable — the caller is either
misconfigured (wrong signing key) or the request is a forgery. The plug maps
this to a 4xx and lets the request fail closed; there is **no** recovery path
(CLAUDE.md "Things Not To Do" #5). This struct mirrors the no-recovery
contract of core `Mailglass.SignatureError` while staying package-local.

It follows the same closed-`:type` discipline as
`MailglassInbound.MIMEError` — a closed `:type` atom set, a
`[:type, :message, :cause, :context, :provider]` `defexception`, and a
`Jason.Encoder` derivation that **excludes `:cause` and `:provider`** so signing
secrets and raw payload fragments never leak into serialized output (logs,
telemetry, admin). Match on the struct and `:type`, never on the message string.

This struct is **package-local**: it does NOT implement the core
`Mailglass.Error` behaviour and does NOT join `Mailglass.Error`'s `@type`
union (that behaviour and union live in `mailglass` core). It is a plain
`defexception` with a `__types__/0` contract helper and a `new/2` builder.

## Types

- `:bad_signature` — HMAC/X.509 signature math did not verify against the
  configured key (forged or tampered request).
- `:missing_header` — a required signing field/header is absent from the request.
- `:malformed_header` — a signing field/header is present but cannot be parsed
  (bad Base64, missing prefix, unknown signature version).
- `:timestamp_skew` — the signed timestamp is outside the acceptable tolerance
  window (Mailgun replay-window protection).
- `:subscribe_url_untrusted` — an SNS `SubscribeURL`/`SigningCertURL` failed
  `TrustPolicy` validation (SSRF / hijack guard).

## Per-kind Fields

- `:provider` — the provider atom (`:mailgun`, `:ses`, …) that rejected the
  request. Excluded from JSON output.

See `docs/api_stability.md` (mailglass_inbound) for the locked `:type` atom
set. Adding a value requires a CHANGELOG entry + `@since` annotation (minor
version bump); removing a value requires a major version bump.

# `t`
*since 0.2.0* 

```elixir
@type t() :: %MailglassInbound.SignatureError{
  __exception__: true,
  cause: term() | nil,
  context: map(),
  message: String.t(),
  provider: atom() | nil,
  type:
    :bad_signature
    | :missing_header
    | :malformed_header
    | :timestamp_skew
    | :subscribe_url_untrusted
}
```

# `__types__`
*since 0.2.0* 

Returns the closed set of valid `:type` atoms. Tested against `docs/api_stability.md`.

# `new`
*since 0.2.0* 

```elixir
@spec new(atom(), keyword()) :: t()
```

Build a `MailglassInbound.SignatureError` struct.

Validates `type` against the closed `__types__/0` set (raises `ArgumentError`
otherwise) and formats a per-type, brand-voiced message. Mirrors core
`Mailglass.SignatureError.new/2` semantics while staying package-local.

## Options

- `:cause` — an underlying exception/term to wrap (kept out of JSON output).
- `:context` — a map of non-PII metadata about the request.
- `:provider` — the provider atom that rejected the request (kept out of JSON).

---

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