Generates deterministic idempotency keys for webhook deduplication and event-ledger entries.
Keys follow the format locked in CORE-05:
for_webhook_event(provider, event_id)→"provider:event_id"for_provider_message_id(provider, message_id)→"provider:msg:message_id"
The msg: infix on provider-message-id keys keeps the two namespaces
disjoint so a webhook event id and a provider message id that happen to
share a string value never collide in the UNIQUE partial index.
Sanitization (T-IDEMP-001)
Keys are derived from provider-supplied strings that reach us across a trust boundary. Two normalization passes run on every key:
- Non-ASCII-printable bytes (outside
0x20..0x7E) are stripped — control characters, DEL, and UTF-8 continuation bytes alike. The resulting key is pure printable ASCII. - The key is truncated at
512bytes so malicious or malformed input cannot balloon the row size or the unique-index b-tree.
Both passes happen before the key is written to the events ledger, so the ledger itself never sees a non-printable byte.
Summary
Functions
Builds an idempotency key for a provider-assigned message id.
Builds an idempotency key for a webhook event.
Builds a per-batch-event idempotency key for SendGrid batch payloads.
Functions
Builds an idempotency key for a provider-assigned message id.
Examples
iex> Mailglass.IdempotencyKey.for_provider_message_id(:sendgrid, "SG.abc")
"sendgrid:msg:SG.abc"
Builds an idempotency key for a webhook event.
Examples
iex> Mailglass.IdempotencyKey.for_webhook_event(:postmark, "evt_abc")
"postmark:evt_abc"
iex> Mailglass.IdempotencyKey.for_webhook_event(:postmark, "evt abc")
"postmark:evtabc"
@spec for_webhook_event(atom(), String.t(), non_neg_integer()) :: String.t()
Builds a per-batch-event idempotency key for SendGrid batch payloads.
Each event in a batch gets "#{provider}:#{provider_event_id}:#{index}"
so duplicate inserts of the same batch event collapse via the
mailglass_events.idempotency_key partial UNIQUE index (Phase 2 Plan
05 DDL). Single-event Postmark payloads use for_webhook_event/2
without the index suffix.
Phase 4 extension per CONTEXT line 343 (Plan 04-06).
Examples
iex> Mailglass.IdempotencyKey.for_webhook_event(:sendgrid, "evt_X", 0)
"sendgrid:evt_X:0"
iex> Mailglass.IdempotencyKey.for_webhook_event(:postmark, "abc-123", 5)
"postmark:abc-123:5"