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.
Add your own Preset
Section titled “Add your own Preset”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.
Test a Preset before using it
Section titled “Test a Preset before using it”# 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 namesphericon preset test postmark
# run verification and transform on a sample of your ownsphericon preset test ./acme-relay.yaml --payload ./sample.json \ --header 'X-Signature=sha256=...' --param secret=hunter2Without --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 file format (version 1)
Section titled “The file format (version 1)”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:
sphericon preset schema > preset-1.schema.json# yaml-language-server: $schema=./preset-1.schema.jsonversion: 1name: acme-relayparams: …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. |
verify
Section titled “verify”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.
transform
Section titled “transform”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 ofbounce,complaint,delivery,delayordrop. Adeliverymeans the receiving server accepted the message, adelaythat the provider is still retrying, adropthat 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 theMessage-IDheader 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.