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.
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
Delivered
The notification, your quota, and its delivery job are written in one transaction. A retried request gets the original answer back.
Workers claim jobs with a lease. Temporary failures retry with backoff. Permanent failures stop.
Every attempt leaves evidence. Delivery, bounce, and complaint events reach you as signed webhooks.
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.
Delivered straight to the recipient’s mail server and signed with DKIM from your verified domain. Hard bounces and complaints suppress the address.
Segments are counted before sending, with a delivery receipt for each one. STOP and START update consent for that sender.
A feed for each person, with unread counts, and read and archive actions that are safe to repeat.
Send a notification with an idempotency key. Charon answers 202 Accepted
once the notification and its delivery job are stored together.
GET /v1/notifications/{id} returns state and timestamps, never the recipient or content.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" }
}
}
curl "$CHARON_URL/v1/notifications" \
-H "Authorization: Bearer $CHARON_API_KEY" \
-H "Idempotency-Key: order-42" \
-H "Content-Type: application/json" \
-d '{
"channel": { "type": "in_app", "recipient_id": "user-1" },
"body": "Your order is ready."
}'
const response = await fetch(`${CHARON_URL}/v1/notifications`, {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.CHARON_API_KEY}`,
'idempotency-key': `order-${order.id}`,
'content-type': 'application/json',
},
body: JSON.stringify({
channel: { type: 'in_app', recipient_id: user.id },
body: 'Your order is ready.',
}),
});
// response.status === 202
202 Accepted Idempotency-Replayed: false
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.
Ask the status API with the notification’s ID, or subscribe to signed webhooks for delivery, bounce, and complaint events.
Yes. Send the same request with the same idempotency key and Charon returns the original answer instead of sending again.
Only what it needs to deliver and prove delivery. The status API never returns recipients or content, and logs never contain them.
Rust services that share one PostgreSQL database, which is also the queue. API keys are stored as peppered digests, and every release is signed.