Mursil Documentation

Delivery and operations

The caller receives an accepted response. Delivery happens after that, on the worker. Operators watch the result. They do not sit on the provider’s HTTP call.

States

The platform state machine is explicit. Handlers do not invent status strings.

Received
Accepted
Rejected
Rendering
RenderFailed
QueuedForDelivery
Delivering
Sent
RetryScheduled
Failed
DeadLettered
Suppressed
Cancelled

Typical path: Accepted, Rendering, QueuedForDelivery, Delivering, Sent. A transient provider failure goes to RetryScheduled and later back to QueuedForDelivery. Exhausted retries become DeadLettered. A suppression match can move an accepted message to Suppressed. A scheduled message can be Cancelled before it sends.

Retries

The default schedule is safe and configurable.

AttemptDelay
1immediate
21 minute
35 minutes
415 minutes
51 hour

The worker claims due work from Postgres with a lease (ClaimedUntil, FOR UPDATE SKIP LOCKED). Two workers do not send the same due row.

Routing and failover

MursilProviderRouter prefers the application default connection, then purpose, region, and tenant default or failover. Dispatch can re-route with the same eligibility rules. The policy used for a delivery is stored on MessageDelivery.ProviderRoutingPolicy.

What operators do with a failure

ActionRouteWho
List triageGET /api/v1/mursil/messages/deliveries/triagesupport
Open one deliveryGET /api/v1/mursil/messages/deliveries/{deliveryId}support
RetryPOST .../retryoperators
SuppressPOST .../suppressoperators
ResolvePOST .../resolveoperators

The portal page for this queue is under Operations, below Trace. An empty capture means no failed deliveries were returned. A rising failure rate or dead-letter backlog is the reason to open that page. See the screenshots in Admin portal.

Suppressions and preferences

Suppression rows are an exchange of hashed recipients, not raw addresses. Import JSON is an array of { scope, reason, recipientHash }. Export downloads the current rows.

Recipients manage marketing and non-critical transactional mail through a signed link, GET /p/{token} and POST /p/{token} on Server. The token is HMAC, via Mursil:Tracking. Security mail is not suppressed there. One-click unsubscribe is POST /api/v1/mursil/preferences/one-click.

Open and click tracking is off until an operator turns it on and saves. The endpoints are GET /t/o/{token} and GET /t/c/{token}.

Audit and events

Operator actions are audited. GET /api/v1/mursil/audit lists records for operators.

When message, template, provider, or suppression state changes, Mursil writes a communication.* integration event through the transactional outbox. Payloads are redacted. Attachment bytes stay in MessageAttachments. Accepted events omit the raw file. Mursil does not host the subscriber that delivers those events to Zapier or another broker. That companion is a separate project.

Inbound provider events

Providers can call back. POST /api/v1/mursil/providers/{providerConnectionId}/events expects X-Mursil-Provider-Signature and X-Mursil-Provider-Timestamp. The signing secret on the connection is a reference, the same env: or secret: form as the send credential, and it is optional. A missing webhook signing secret is an honest local state, not a healthy production sender.

Inbound replies from a mailbox are a different route: authenticated POST /api/v1/mursil/messages/replies. Mursil does not host a mailbox. Outbound SMTP and HTTP providers can set Reply-To.