Skip to content

Presets

A Preset describes one provider’s inbound callbacks: how a request is proven to come from that provider, and how its payload becomes Delivery notices (a bounce, a spam complaint, a delivery, a delay or a drop for one address). Sphericon ships Presets for generic, ses, postmark and mailgun. On a self-hosted Instance you can add your own for a provider the project has no Preset for.

Only the Instance operator supplies Presets: a Workspace never does, and the API accepts none. A Preset’s transform runs inside the server process, so it is as trusted as the Instance’s configuration.

Put one YAML file per Preset in a directory and point SPHERICON_PRESETS_DIR at it (see the environment table). Every *.yaml and *.yml file at the root of the directory loads at start, beside the built-in ones. A file that does not parse, does not validate against the schema, has a transform that does not compile, or reuses a built-in name stops the start with its file and line. Then choose the Preset by name on an Integration.

Terminal window
# run the cases the file carries in its own `tests`
sphericon preset test ./acme-relay.yaml
# built-in Presets run through the same command, by name
sphericon preset test postmark
# run verification and transform on a sample of your own
sphericon preset test ./acme-relay.yaml --payload ./sample.json \
--header 'X-Signature=sha256=...' --param secret=hunter2

Without --payload it prints PASS or FAIL for each case in tests and exits non-zero if any failed. With --payload it prints the Delivery notices the sample yields, or the reason it was refused. --header name=value and --param name=value repeat; --now sets the clock (an RFC 3339 time) for a signature with a timestamp tolerance.

The format carries a version and changes only between Majors, so your files keep working in between. sphericon preset schema prints its JSON Schema (its $id is only an identifier, nothing is served there). Save it next to your file and point your editor at it for validation as you write:

Terminal window
sphericon preset schema > preset-1.schema.json
# yaml-language-server: $schema=./preset-1.schema.json
version: 1
name: acme-relay
params: …
verify: …
transform: …
tests: …
Field Meaning
version The format version, 1.
name Unique across the Instance; [a-z][a-z0-9_-]*. A custom Preset cannot reuse a built-in name.
description Optional one line.
params A JSON Schema (draft 2020-12) of what the Owner enters on the Integration. A secret is a string marked writeOnly.
verify How the request is proven authentic: one kind from the closed set below.
transform A gojq program. Input {body, headers}; output an array of Delivery notices.
tests At least one case: request (headers, body), params, optional now, and the expected notices or refused.

Nothing reaches the transform unverified. Header schemes check the raw bytes before any parsing; body schemes read their signature from the body, so they parse under the body limit, verify, and only then transform. The body is parsed as JSON whatever its Content-Type; form-encoded bodies are not supported.

Kind What it checks
hmac_header HMAC (algorithm, encoding, optional prefix) over a signed template ({body}, {header.Name}), optional timestamp tolerance
standard_webhooks The three Standard Webhooks headers, five-minute tolerance
token A shared token in a header (optional prefix), or basic auth (basic: true)
hmac_body HMAC over named signed_fields of the JSON body, the signature in signature_field, optional timestamp field and tolerance

Each kind names the params property that holds its secret.

A pure function with a fixed output: no environment, no input, no modules, and no debug, stderr, now or input_filename (a Preset that calls one is refused at load). Each element it returns is {id, destination, kind, permanent?, sending_domain?, message_id?, reason?}:

  • id: the provider’s event id; with the destination it dedupes a redelivery.
  • destination: the address (trimmed and lower-cased by Sphericon).
  • kind: one of bounce, complaint, delivery, delay or drop. A delivery means the receiving server accepted the message, a delay that the provider is still retrying, a drop that the provider never attempted or gave up on the send.
  • permanent: for a bounce, whether it is hard. Only permanent bounces and complaints enter Suppression; a soft bounce, a delivery, a delay and a drop never suppress.
  • sending_domain: optional, the domain the message was sent from.
  • message_id: optional, the provider’s id of the message. Sphericon matches it to the message it sent (the SES message id, or for SMTP the Message-ID header it set), so the notice is linked to that Broadcast or Automation email and stamps its delivery result. Without it, or without a match, the notice stays at the level of the address.
  • reason: optional, a short provider explanation of a bounce, delay or drop; at most 500 characters.

A notice becomes an email.delivered, email.delayed, email.dropped, email.bounced or email.complained Event. The first notice of each of delivered, bounced and complained also stamps the message once, and reports read those stamps, so a repeated or redelivered notice changes nothing. The Complaint rate is computed over delivered mail, so a Preset that never returns delivery leaves it undefined for that Workspace.

Sphericon validates what the program returns and never trusts its shape. A run stops at a timeout (100 ms), and the body, the program (16 KiB) and the number of results (100) are limited. Return an empty array for an event the Preset does not handle: the provider still gets a 2xx.