Notifications with a receipt.

One API for email, SMS, and in-app messages. Charon accepts each request once, carries it to the person it’s for, and keeps evidence of what happened.

Notification 0193a7c2

Email to acme.example

  1. Accepted 202, key order-42
  2. Queued outbox job 1 of 1
  3. Dispatching mx1.example.net, TLS
  4. Accepted by MX 250 2.0.0
  5. Webhook email.submitted, signed

Delivered

Every message crosses once.

  1. Accept once

    The notification, your quota, and its delivery job are written in one transaction. A retried request gets the original answer back.

  2. Deliver carefully

    Workers claim jobs with a lease. Temporary failures retry with backoff. Permanent failures stop.

  3. Keep the receipt

    Every attempt leaves evidence. Delivery, bounce, and complaint events reach you as signed webhooks.

Unknown

Sometimes a mail server or SMS gateway drops the connection after taking a message. Charon marks it unknown instead of guessing and sending it twice. It stays unknown until delivery evidence or an operator settles it.

Email, SMS, and in-app. One lifecycle.

Email, sent direct

Delivered straight to the recipient’s mail server and signed with DKIM from your verified domain. Hard bounces and complaints suppress the address.

SMS you can predict

Segments are counted before sending, with a delivery receipt for each one. STOP and START update consent for that sender.

In-app feeds

A feed for each person, with unread counts, and read and archive actions that are safe to repeat.

One request to send.

Send a notification with an idempotency key. Charon answers 202 Accepted once the notification and its delivery job are stored together.

Retries never double-send
The same key returns the first answer. A new body with an old key gets a 409.
Your account comes from your key
Requests can’t name another account, and every query is scoped to yours.
Status without personal data
GET /v1/notifications/{id} returns state and timestamps, never the recipient or content.
Signed webhooks
Every event carries a timestamped HMAC signature with a secret you can rotate.
POST /v1/notifications
Authorization: Bearer chr_live.<id>.<secret>
Idempotency-Key: order-42
Content-Type: application/json

{
  "channel": {
    "type": "email",
    "to": "[email protected]",
    "sender_domain_id": "8d3f…",
    "template_version_id": "0c21…",
    "variables": { "order_id": "42" }
  }
}

202 Accepted Idempotency-Replayed: false

Questions.

What does early access include?

An API key for the hosted service, and help setting up your sending domains and SMS senders. We start with the channels and volume you tell us about.

How do I know what happened to a message?

Ask the status API with the notification’s ID, or subscribe to signed webhooks for delivery, bounce, and complaint events.

Can I retry a request safely?

Yes. Send the same request with the same idempotency key and Charon returns the original answer instead of sending again.

What does Charon keep about my recipients?

Only what it needs to deliver and prove delivery. The status API never returns recipients or content, and logs never contain them.

How is it built?

Rust services that share one PostgreSQL database, which is also the queue. API keys are stored as peppered digests, and every release is signed.

Request access

Tell us what you send and roughly how much. We’ll reply by email with the next steps.

Channels you need

We use these details only to contact you about Charon access.