API reference
A REST API over HTTPS. Requests and responses are JSON, every request is authenticated with an API key, and every error has the same shape. The reference below is drawn from the same table that routes each request, so it lists exactly what the API answers.
Base URL: https://app.rykonmail.com/api/v1
Send your key as Authorization: Bearer rmk_live_.... Each operation names the scope its key must hold. Read Authentication, Errors and Rate limits once; they apply to everything here. The whole API is also published as OpenAPI 3.1.
curl https://app.rykonmail.com/api/v1/domains \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"error": "forbidden",
"message": "this key does not hold audience:write"
}/emailsSends one transactional email: a message your application sends one person because of something they did, such as a receipt, a sign-in link or an order update. It carries no unsubscribe link, so the rules are strict. An address that bounced or marked your mail as spam is never sent to. Someone who unsubscribed may still get account, security, receipt and order mail, but never a notification, and never a message that reads as selling; that is refused with the words that gave it away. Refusals answer 409 and are listed in GET /emails. The from address must be on one of your domains that is set up for sending (400 if the domain is not yours, 409 if it is not ready, 409 if sending is stopped for the account). One address gets at most 20 messages a day, and the account has an hourly limit by plan; either answers 429. Give text, html or both; without text, a plain text part is made from the HTML. Send an idempotencyKey to make retries safe: the same key returns the first message with "repeated": true instead of sending again. The message body is not kept, only who, when, the subject and the outcome. Needs a key holding mail:send.
curl -X POST https://app.rykonmail.com/api/v1/emails \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "orders@northwind-local.test",
"fromName": "Northwind Traders",
"to": "jamie.chen@example.com",
"replyTo": "help@northwind-local.test",
"subject": "Your receipt for order 10482",
"text": "Thanks for your order.\n\nOrder 10482: 2 x Linen apron, $48.00.\nTotal paid: $48.00.",
"html": "<p>Thanks for your order.</p><p>Order 10482: 2 x Linen apron, $48.00.<br>Total paid: <strong>$48.00</strong>.</p>",
"category": "receipt",
"idempotencyKey": "order-10482-receipt"
}'{
"id": "682e5ffd-1528-4b9e-9b81-65903d3d9593",
"category": "receipt",
"from": "orders@northwind-local.test",
"to": "jamie.chen@example.com",
"subject": "Your receipt for order 10482",
"state": "sent",
"messageId": "simulated-fa61a1bd-2389-4b00-ab0c-63ac54867b0c",
"error": null,
"createdAt": "2026-10-30T06:18:05.177Z"
}/emailsThe 100 most recent transactional messages, newest first, including the ones that were refused and why. With them: how many were sent in the last hour and day, how many were refused today, the account's hourly limit and the most one address may get in a day. Bodies are never kept, so they are not here. Needs sending:read.
curl "https://app.rykonmail.com/api/v1/emails?to=jamie.chen%40example.com" \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"messages": [
{
"id": "ba525624-1079-436d-9525-88eb1b013547",
"category": "order",
"from": "orders@northwind-local.test",
"to": "jamie.chen@example.com",
"subject": "20% off everything this weekend",
"state": "refused",
"messageId": null,
"error": "jamie.chen@example.com unsubscribed and this reads as marketing (% off, shop now); transactional mail must not sell",
"createdAt": "2026-10-30T06:18:05.232Z"
},
{
"id": "682e5ffd-1528-4b9e-9b81-65903d3d9593",
"category": "receipt",
"from": "orders@northwind-local.test",
"to": "jamie.chen@example.com",
"subject": "Your receipt for order 10482",
"state": "sent",
"messageId": "simulated-fa61a1bd-2389-4b00-ab0c-63ac54867b0c",
"error": null,
"createdAt": "2026-10-30T06:18:05.177Z"
}
],
"lastHour": 1,
"lastDay": 27,
"refusedToday": 25,
"hourlyLimit": 2000,
"perRecipientPerDay": 20
}/contactsContacts, newest first, with the account's totals by status (the totals count everyone, not just this page). Needs audience:read.
curl "https://app.rykonmail.com/api/v1/contacts?search=jamie&limit=2" \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"contacts": [
{
"id": "00410db1-2799-4dd4-afef-11f95befa5af",
"email": "jamie.chen@example.com",
"name": "Jamie Chen",
"status": "subscribed",
"tags": [
"customer",
"vip"
],
"consent_source": "api:Checkout opt-in",
"consent_at": "2026-10-30T06:13:46.762Z",
"confirmed_at": "2026-10-30T06:13:46.762Z",
"unsubscribed_at": null,
"created_at": "2026-10-30T06:13:46.762Z"
}
],
"totals": {
"subscribed": 3,
"pending": 7,
"unsubscribed": 1
}
}/contactsAdds someone to your audience, with the consent they gave recorded word for word: what they agreed to, where, and optionally from which IP address. Answers 201 for a new contact. If the address is already subscribed (or pending, when you ask for double opt-in again), the contact is filled in rather than replaced: a name if given, new tags and attributes merged in, nothing taken away; that answers 200 with "created": false. With doubleOptIn the person is sent a confirmation email and stays pending until they use its link, which expires in 7 days; without it they are subscribed at once and any automation that starts on subscribing starts. Two things only the person can do: an address that unsubscribed or asked to be erased comes back only through their own confirmation, so it answers 409 unless doubleOptIn is true; and an address that bounced or marked your mail as spam can never be added again (409). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/contacts \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "jamie.chen@example.com",
"name": "Jamie Chen",
"tags": [
"customer",
"newsletter"
],
"attributes": {
"plan": "pro",
"city": "Austin"
},
"consent": {
"text": "Yes, send me product news and offers. I can unsubscribe at any time.",
"source": "Checkout opt-in",
"ip": "203.0.113.24"
}
}'{
"id": "00410db1-2799-4dd4-afef-11f95befa5af",
"email": "jamie.chen@example.com",
"name": "Jamie Chen",
"status": "subscribed",
"tags": [
"customer",
"newsletter"
],
"attributes": {
"city": "Austin",
"plan": "pro"
},
"consent_source": "api:Checkout opt-in",
"consent_at": "2026-10-30T06:13:46.762Z",
"confirmed_at": "2026-10-30T06:13:46.762Z",
"unsubscribed_at": null,
"created_at": "2026-10-30T06:13:46.762Z",
"created": true
}/contacts/import/previewReads a CSV file without importing anything: its columns, what each would become (guessed when you send no mapping), the first rows, and how many people are new, already on the list, or suppressed and so would be skipped, with the first 50 invalid addresses and duplicates. The file can be up to 5 MB and 50,000 rows. A mapping entry is email, name, first_name, last_name, tags, ignore, or field:<key> for a field of your own. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/contacts/import/preview \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"csv": "email,first_name,last_name,company\nava.patel@example.com,Ava,Patel,Acme\nnoah.kim@example.com,Noah,Kim,Globex\nnot-an-address,Bad,Row,\nava.patel@example.com,Ava,Patel,Acme\n"
}'{
"headers": [
"email",
"first_name"
],
"mapping": [
"email",
"first_name"
],
"sample": [
[
"ava.patel@example.com",
"Ava"
],
[
"noah.kim@example.com",
"Noah"
]
],
"total": 4,
"valid": 2,
"new": 2,
"existing": 0,
"suppressed": 0,
"invalid": [
{
"line": 4,
"value": "not-an-address"
}
],
"invalidCount": 1,
"duplicates": [
{
"line": 5,
"email": "ava.patel@example.com"
}
],
"duplicateCount": 1,
"needsEmailColumn": false
}/contacts/importImports a CSV file. An upload is never consent, so you declare where these people came from: basis "subscribed" is your own statement that they agreed, recorded word for word on every contact created; "unconfirmed" keeps them pending, and pending contacts are never sent marketing. People already on the list keep their status: an import only adds tags and fills empty fields. Unsubscribed, bounced and complained addresses are skipped. The mapping must have one entry per column and exactly one email column. Every import can be undone. Same limits as the preview. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/contacts/import \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"csv": "email,first_name,last_name,company\nava.patel@example.com,Ava,Patel,Acme\nnoah.kim@example.com,Noah,Kim,Globex\nnot-an-address,Bad,Row,\nava.patel@example.com,Ava,Patel,Acme\n",
"fileName": "webinar-signups.csv",
"mapping": [
"email",
"first_name",
"last_name",
"field:company"
],
"basis": "unconfirmed",
"consentText": "People who registered for our October webinar and ticked the box to hear from us.",
"declaration": true,
"tags": [
"webinar"
]
}'{
"id": "7ba4f9fa-30c1-4d1a-807e-0d8c3f057d6b",
"rows": 4,
"created": 2,
"updated": 0,
"skippedInvalid": 1,
"skippedDuplicate": 1,
"skippedSuppressed": 0,
"basis": "unconfirmed"
}/contacts/importsThe 50 most recent imports, newest first, with their counts, their declaration and whether they were undone. Needs audience:read.
curl https://app.rykonmail.com/api/v1/contacts/imports \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"imports": [
{
"id": "7ba4f9fa-30c1-4d1a-807e-0d8c3f057d6b",
"file_name": "webinar-signups.csv",
"basis": "unconfirmed",
"consent_text": "People who registered for our October webinar and ticked the box to hear from us.",
"tags": [
"webinar"
],
"rows": 4,
"created": 2,
"updated": 0,
"skipped_invalid": 1,
"skipped_duplicate": 1,
"skipped_suppressed": 0,
"created_by": "key sa1pteyz44qbm",
"created_at": "2026-10-30T06:13:57.624Z",
"undone_at": null,
"undone_by": null,
"undo_kept": null
}
]
}/contacts/imports/{id}/undoRemoves the people an import created who have not been sent anything since and have not signed up again by themselves, and takes off the tags it added to people who were already there. "kept" counts the people it created who stay. An import is undone once; a second try answers 409. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/contacts/imports/00410db1-2799-4dd4-afef-11f95befa5af/undo \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"removed": 2,
"kept": 0
}/contacts/{id}One contact, with the consent on record (its text, source, time and IP address) and its 100 most recent events. Needs audience:read.
curl https://app.rykonmail.com/api/v1/contacts/00410db1-2799-4dd4-afef-11f95befa5af \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "00410db1-2799-4dd4-afef-11f95befa5af",
"email": "jamie.chen@example.com",
"name": "Jamie Chen",
"status": "subscribed",
"tags": [
"customer",
"vip"
],
"attributes": {
"plan": "business"
},
"consent_source": "api:Checkout opt-in",
"consent_text": "Yes, send me product news and offers. I can unsubscribe at any time.",
"consent_at": "2026-10-30T06:13:46.762Z",
"consent_ip": "203.0.113.24",
"confirmed_at": "2026-10-30T06:13:46.762Z",
"unsubscribed_at": null,
"created_at": "2026-10-30T06:13:46.762Z",
"events": [
{
"kind": "updated",
"detail": {
"by": "key sa1pteyz44qbm",
"via": "api"
},
"created_at": "2026-10-30T06:13:46.890Z"
},
{
"kind": "updated",
"detail": {
"by": "key sa1pteyz44qbm",
"via": "api"
},
"created_at": "2026-10-30T06:13:46.833Z"
}
]
}/contacts/{id}Changes a contact's name, tags and attributes. It never changes status: only the person's consent and unsubscribing do that. Attributes are merged in, and an attribute set to null is removed. Fields other than these are refused with 400. Needs audience:write.
curl -X PATCH https://app.rykonmail.com/api/v1/contacts/00410db1-2799-4dd4-afef-11f95befa5af \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Jamie Chen",
"addTags": [
"vip"
],
"removeTags": [
"newsletter"
],
"attributes": {
"plan": "business",
"city": null
}
}'{
"id": "00410db1-2799-4dd4-afef-11f95befa5af",
"email": "jamie.chen@example.com",
"name": "Jamie Chen",
"status": "subscribed",
"tags": [
"customer",
"vip"
],
"attributes": {
"plan": "business"
},
"consent_source": "api:Checkout opt-in",
"consent_at": "2026-10-30T06:13:46.762Z",
"confirmed_at": "2026-10-30T06:13:46.762Z",
"unsubscribed_at": null,
"created_at": "2026-10-30T06:13:46.762Z"
}/contacts/{id}/unsubscribeUnsubscribes someone on their behalf, for example when they ask you by email. They are added to your suppression list and taken out of any automation they are in. Only their own confirmation can bring them back. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/contacts/00410db1-2799-4dd4-afef-11f95befa5af/unsubscribe \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "00410db1-2799-4dd4-afef-11f95befa5af",
"status": "unsubscribed"
}/contacts/{id}Erasure, for a request to be forgotten: the contact and their history are deleted. Their address stays on your suppression list so they are never mailed again by mistake; adding them back takes their own confirmation (POST /contacts with doubleOptIn). Needs audience:write.
curl -X DELETE https://app.rykonmail.com/api/v1/contacts/00410db1-2799-4dd4-afef-11f95befa5af \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"erased": true
}/contacts/{id}/topicsEvery topic on the account, with when this person opted out of it and how, or null if they still receive it. Needs audience:read.
curl https://app.rykonmail.com/api/v1/contacts/4d3e3999-dcc6-4f7f-898c-e7ab1e971c3e/topics \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"topics": [
{
"id": "4d3e3999-dcc6-4f7f-898c-e7ab1e971c3e",
"name": "Offers 88153",
"opted_out_at": null,
"source": null
},
{
"id": "ffbf4d82-d5fe-401a-b7c1-aea54f759542",
"name": "Offers 76520",
"opted_out_at": null,
"source": null
}
]
}/contacts/{id}/topics/{topicId}Records a choice the person made about one topic: whether they receive it. Use it only on their request; the contact's history keeps who recorded it. Unsubscribing still stops everything; a topic only narrows. Needs audience:write.
curl -X PUT https://app.rykonmail.com/api/v1/contacts/00410db1-2799-4dd4-afef-11f95befa5af/topics/TOPIC_ID \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"receives": false
}'{
"receives": false
}/topicsThe account's topics, oldest first, with how many people opted out of each and how many campaigns use it. Needs audience:read.
curl https://app.rykonmail.com/api/v1/topics \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"topics": [
{
"id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"name": "Product updates",
"description": "New features and fixes, about once a month.",
"created_at": "2026-10-30T06:13:45.895Z",
"opted_out": 0,
"campaigns": 0
}
]
}/topicsAdds a kind of email people can opt out of on its own, such as product updates or offers. Everyone subscribed receives a topic until they opt out of it; a campaign that names a topic leaves out those who did. Names are unique on the account (400 if taken), and an account can have 30 topics (400 beyond that). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/topics \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Product updates",
"description": "New features and changes, about once a month."
}'{
"id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"name": "Product updates",
"description": "New features and changes, about once a month.",
"created_at": "2026-10-30T06:13:45.895Z",
"opted_out": 0,
"campaigns": 0
}/topics/{id}Renames a topic or changes its description; send only what changes. A name already used answers 400. Needs audience:write.
curl -X PATCH https://app.rykonmail.com/api/v1/topics/bea753b0-6132-4bc2-bc2a-18ff86ed0079 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "New features and fixes, about once a month."
}'{
"id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"name": "Product updates",
"description": "New features and fixes, about once a month.",
"created_at": "2026-10-30T06:13:45.895Z",
"opted_out": 0,
"campaigns": 0
}/topics/{id}Archives a topic. It is never deleted, because past campaigns and people's choices still point at it; draft campaigns that named it no longer do. Needs audience:write.
curl -X DELETE https://app.rykonmail.com/api/v1/topics/bea753b0-6132-4bc2-bc2a-18ff86ed0079 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"archived": true
}/formsThe account's sign-up forms, newest first, with how many contacts joined through each and the five sites it was most recently seen on. siteOrigin is where hosted forms live (/f/<public_id>). Needs audience:read.
curl https://app.rykonmail.com/api/v1/forms \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"forms": [
{
"id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"public_id": "9QAqDsJMl79DfFKY",
"name": "API docs example",
"heading": "Get our monthly letter",
"description": "New products and studio notes, about once a month.",
"button_label": "Sign me up",
"consent_text": "I agree to receive emails from us. I can unsubscribe at any time with the link in every email.",
"ask_name": true,
"double_opt_in": true,
"success_message": "Thanks. Check your inbox to confirm.",
"redirect_url": null,
"reply_to": null,
"tags": [
"newsletter"
],
"active": false,
"allowed_sites": [
"example.com"
],
"created_at": "2026-10-30T06:14:07.200Z",
"updated_at": "2026-10-30T06:14:07.475Z",
"contacts": 0,
"placements": []
}
],
"siteOrigin": "http://localhost:4320"
}/formsMakes a sign-up form. People who submit it become contacts with the form's consent text recorded; with double opt-in (the default) they are subscribed only once they confirm. Creating a live form beyond your plan's live form limit answers 402; create it with active false, or switch another off. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/forms \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example",
"heading": "Get our monthly letter",
"description": "New products and studio notes, about once a month.",
"tags": [
"newsletter"
],
"allowedSites": [
"example.com"
],
"active": false
}'{
"id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"public_id": "9QAqDsJMl79DfFKY",
"name": "API docs example",
"heading": "Get our monthly letter",
"description": "New products and studio notes, about once a month.",
"button_label": "Subscribe",
"consent_text": "I agree to receive emails from us. I can unsubscribe at any time with the link in every email.",
"ask_name": true,
"double_opt_in": true,
"success_message": "Thanks. Check your inbox to confirm.",
"redirect_url": null,
"reply_to": null,
"tags": [
"newsletter"
],
"active": false,
"allowed_sites": [
"example.com"
],
"created_at": "2026-10-30T06:14:07.200Z",
"updated_at": "2026-10-30T06:14:07.200Z"
}/forms/{id}Changes a form; send only what changes. Switching a form on counts against your plan's live form limit and answers 402 when there is no room; editing or switching off never does. Needs audience:write.
curl -X PATCH https://app.rykonmail.com/api/v1/forms/a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"buttonLabel": "Sign me up",
"successMessage": "Thanks. Check your inbox to confirm."
}'{
"id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"public_id": "9QAqDsJMl79DfFKY",
"name": "API docs example",
"heading": "Get our monthly letter",
"description": "New products and studio notes, about once a month.",
"button_label": "Sign me up",
"consent_text": "I agree to receive emails from us. I can unsubscribe at any time with the link in every email.",
"ask_name": true,
"double_opt_in": true,
"success_message": "Thanks. Check your inbox to confirm.",
"redirect_url": null,
"reply_to": null,
"tags": [
"newsletter"
],
"active": false,
"allowed_sites": [
"example.com"
],
"created_at": "2026-10-30T06:14:07.200Z",
"updated_at": "2026-10-30T06:14:07.475Z"
}/forms/{id}/check-installFetches a page of your site and looks for this form in it: placed with the script, a button that opens it, your own form connected to it, an iframe, or a link. Says how it was found and whether the script line is missing, or why it was not found. Only public pages can be fetched. Needs audience:read.
curl -X POST https://app.rykonmail.com/api/v1/forms/a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740/check-install \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/"
}'{
"found": false,
"url": "https://example.com/",
"reason": "The form is not in this page as it was sent. If your site builds pages in the browser, it may still show once the page h..."
}/forms/{id}Deletes a sign-up form. Its hosted link and every embed stop answering; the contacts it brought in stay in your audience and keep the record of what they agreed to, which is stored with them. Refused with 409 while an automation starts from the form, naming those automations: deleting the form would turn them into automations that start for every sign-up, so change or delete them first. 404 if the form is not in your account. Needs audience:write.
curl -X DELETE https://app.rykonmail.com/api/v1/forms/a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"deleted": true
}/campaignsEvery campaign, newest first, with its state, whether its last check passed, and how many recipients it has and how many were sent. postalAddress is the account's, which every campaign carries. Needs audience:read.
curl https://app.rykonmail.com/api/v1/campaigns \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"campaigns": [
{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": "key sa1pteyz44qbm",
"state": "sent",
"state_reason": null,
"scheduled_at": "2026-10-30T08:15:19.116Z",
"started_at": "2026-10-30T06:15:20.212Z",
"finished_at": "2026-10-30T06:15:20.411Z",
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:15:20.411Z",
"kind": "marketing",
"validation_ok": true,
"validation_blocking": 0,
"recipients": 0,
"sent": 0
}
],
"postalAddress": "12 Studio Lane, Austin, TX 78701"
}/campaignsCreates a draft. Its design starts from a template, from an email's HTML you send, or from a starter design, in your brand look unless you ask for the original. A domainId that is not one of yours is left empty. Set the topic, segment, subject test and brief afterwards with PATCH /campaigns/:id. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"fromName": "Northwind Traders",
"fromLocal": "news",
"domainId": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"replyTo": "hello@northwind-local.test",
"audienceTags": [
"vip"
]
}'{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": null,
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": null,
"state": "draft",
"state_reason": null,
"scheduled_at": null,
"started_at": null,
"finished_at": null,
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:14:34.949Z"
}/campaigns/import-htmlReads an existing email's HTML into a design without creating anything, and says what came across and what was left out, so you can check it before starting a campaign from it (send the same html to POST /campaigns). Up to 900,000 characters. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/import-html \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<html><head><title>Autumn sale</title></head><body><h1>Autumn sale</h1><p>Hi there, everything in the shop is 20% off this week.</p><a href=\"https://northwind-local.test/sale\" style=\"background:#0f766e;color:#fff;padding:12px 20px\">Shop the sale</a></body></html>"
}'{
"design": {
"version": 1,
"theme": {
"background": "#f4f6fb",
"content": "#ffffff",
"text": "#2b3245",
"heading": "#0b0d14",
"accent": "#0f766e",
"onAccent": "#ffffff",
"muted": "#7a8296",
"font": "system",
"radius": 12
},
"blocks": [
{
"id": "b-mvukw77w-5",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "heading",
"text": "Autumn sale",
"level": 1,
"align": "left",
"color": ""
},
{
"id": "b-mvukw77w-6",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "text",
"text": "Hi there, everything in the shop is 20% off this week.",
"align": "left",
"size": 16,
"color": ""
}
],
"footerNote": "",
"plainText": ""
},
"title": "Autumn sale",
"preheader": "",
"counts": {
"headings": 1,
"paragraphs": 1,
"images": 0,
"buttons": 1
},
"notes": [
"The layout is now the Studio’s own: check the order, and set colours and fonts in Email style."
]
}/campaigns/{id}One campaign, with its recipients by state, opens and clicks, and how many people its audience reaches now. Needs audience:read.
curl https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": "key sa1pteyz44qbm",
"state": "draft",
"state_reason": null,
"scheduled_at": null,
"started_at": null,
"finished_at": null,
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:14:45.827Z",
"stats": {},
"engagement": {
"opened": 0,
"clicked": 0
},
"audienceSize": 0
}/campaigns/{id}Changes a draft; send only what changes. Anything but a draft answers 409 (cancel a scheduled campaign to edit it). A topicId that is not one of yours answers 400. The brief is merged field by field. Changing anything after approval means approving again before it can be scheduled. Needs audience:write.
curl -X PATCH https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Our autumn collection is here",
"topicId": "bea753b0-6132-4bc2-bc2a-18ff86ed0079"
}'{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": "key sa1pteyz44qbm",
"state": "draft",
"state_reason": null,
"scheduled_at": null,
"started_at": null,
"finished_at": null,
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:14:45.827Z"
}/campaigns/{id}Deletes a draft that never went out, with its versions and history. Anything that started sending is kept as a record and answers 409. Needs audience:write.
curl -X DELETE https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"deleted": true
}/campaigns/{id}/studioThe campaign's design (theme and blocks), whether its approval still holds, who its audience reaches and who is left out and why, the personal fields it uses, and its last check. Needs audience:read.
curl https://app.rykonmail.com/api/v1/campaigns/b-mvukur45-1/studio \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"design": {
"version": 1,
"theme": {
"background": "#f4f6fb",
"content": "#ffffff",
"text": "#2b3245",
"heading": "#052e2b",
"accent": "#0f766e",
"onAccent": "#ffffff",
"muted": "#7a8296",
"font": "serif",
"radius": 12
},
"blocks": [
{
"id": "b-mvukur45-1",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "header",
"logoSrc": "",
"logoAlt": "Northwind Traders",
"logoWidth": 140,
"text": "Northwind Traders",
"align": "center"
},
{
"id": "b-mvukur45-2",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "heading",
"text": "A headline that says what is new",
"level": 1,
"align": "left",
"color": ""
}
],
"footerNote": "",
"plainText": ""
},
"approval": {
"holds": false,
"versionId": null,
"number": null
},
"audience": {
"matching": 1,
"eligible": 0,
"topic_opted_out": 1,
"suppressed": 0,
"unconfirmed": 0,
"unsubscribed": 0,
"unnamed": 0,
"invalid": 0
},
"fields": [
"first_name",
"name"
],
"validation": null,
"updatedBy": "key sa1pteyz44qbm",
"updatedAt": "2026-10-30T06:14:35.021Z"
}/campaigns/{id}/designReplaces a draft's design with the one sent, checked against the design schema (version, theme, blocks, footerNote, plainText; start from GET /campaigns/:id/studio). Anything but a draft answers 409. A design changed after approval must be approved again. A custom HTML block ({ type: "html", html }, at most 50 KB) is stored only as the sanitiser leaves it; the answer lists each one under html with the HTML kept and what was removed. Needs audience:write.
curl -X PUT https://app.rykonmail.com/api/v1/campaigns/b-mvukur45-1/design \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"design": {
"version": 1,
"theme": {
"background": "#f4f6fb",
"content": "#ffffff",
"text": "#2b3245",
"heading": "#052e2b",
"accent": "#0f766e",
"onAccent": "#ffffff",
"muted": "#7a8296",
"font": "serif",
"radius": 12
},
"blocks": [
{
"id": "b-mvukur45-1",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "header",
"logoSrc": "",
"logoAlt": "Northwind Traders",
"logoWidth": 140,
"text": "Northwind Traders",
"align": "center"
},
{
"id": "b-mvukur45-2",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "heading",
"text": "Our autumn collection is here",
"level": 1,
"align": "left",
"color": ""
},
{
"id": "b-mvukur45-3",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "text",
"text": "Hi {{ name | there }},\n\nTwelve new pieces, made in small batches in our Austin studio.",
"align": "left",
"size": 16,
"color": ""
},
{
"id": "b-mvukur45-4",
"paddingTop": 0,
"paddingBottom": 16,
"background": "",
"hideOnMobile": false,
"type": "button",
"label": "See the collection",
"href": "https://northwind-local.test/autumn",
"align": "left",
"style": "filled",
"color": ""
}
],
"footerNote": "",
"plainText": ""
}
}'{
"savedAt": "2026-10-30T06:15:03.020Z",
"html": []
}/campaigns/{id}/validateRuns the checks a campaign must pass before it can be approved: blocking problems (placeholder text, links that go nowhere, an empty audience and the like) and advice. With deep, every link and image is fetched too. Needs audience:read.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/validate \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"deep": false
}'{
"report": {
"ok": true,
"blocking": [],
"advisory": [
{
"code": "plan-campaigns",
"area": "delivery",
"message": "Without Campaigns, forms and automation you can send 461 more campaign emails in this 30 days; this one goes to 1.",
"fix": "Turn it on in Billing to send without this limit."
}
],
"checkedAt": "2026-10-30T06:15:03.080Z"
},
"audience": {
"matching": 1,
"eligible": 1,
"topic_opted_out": 0,
"suppressed": 0,
"unconfirmed": 0,
"unsubscribed": 0,
"unnamed": 0,
"invalid": 0
},
"bytes": 3667,
"fields": [
"name"
]
}/campaigns/{id}/renderThe message as a recipient would see it, as HTML or plain text (not JSON): for a sample person, one of your contacts, someone with no name (to see the fallbacks) or a very long name. The unsubscribe link is a placeholder. Needs audience:read.
curl "https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/render?format=text&as=sample" \
-H "Authorization: Bearer $RYKON_MAIL_KEY"Northwind Traders
Our autumn collection is here
Hi Alex,
Twelve new pieces, made in small batches in our Austin studio.
See the collection: https://northwind-local.test/autumn
--
You are receiving this because you subscribed to Northwind Traders.
Northwind Traders, 12 Studio Lane, Austin, TX 78701
Unsubscribe: #unsubscribe/campaigns/{id}/testSends one copy to each address, exactly as a send would compile it, with "[Test]" before the subject and an unsubscribe link that goes nowhere. Copies go only to people on your account (400 for anyone else), 30 an hour (429 beyond that). The campaign needs a sending domain that is set up (409). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/test \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": [
"ui-test@rykonmail-local.test"
]
}'{
"results": [
{
"to": "ui-test@rykonmail-local.test",
"sent": true
}
]
}/campaigns/{id}/approveApproves the campaign exactly as it stands and keeps that version. Every blocking check must pass (409 with the report otherwise), and only a draft can be approved. Change anything afterwards and it must be approved again. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/approve \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"approved": true,
"version": 1,
"versionId": "1468cf84-2fb5-4a40-97c0-6edfb1926491"
}/campaigns/{id}/scheduleSends now, or at a time. Only a draft whose approval still covers it as it stands can be scheduled (409 otherwise). Without the Campaigns add-on a campaign must fit in what is left of the free allowance, and with it the list must fit the plan; either answers 402. Scheduling twice never sends twice. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/schedule \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"at": "2026-10-30T08:15:19Z"
}'{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": "key sa1pteyz44qbm",
"state": "scheduled",
"state_reason": null,
"scheduled_at": "2026-10-30T08:15:18.947Z",
"started_at": null,
"finished_at": null,
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:15:19.947Z"
}/campaigns/{id}/pauseStops a campaign part way: nothing still queued goes out; what was already handed to the sending service cannot be called back. "held" is how many copies wait. Only a campaign that is sending can be paused (409). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/pause \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"state": "paused",
"held": 0
}/campaigns/{id}/resumeCarries on sending a paused campaign from where it stopped. Only a paused campaign can be resumed (409). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/resume \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"state": "sending"
}/campaigns/{id}/cancelA scheduled campaign that has not started goes back to being a draft; one that is sending or paused stops and is cancelled. Anything else answers 409. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/cancel \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "9e210b02-8326-4122-a449-2196ccd8cc38",
"name": "API docs example",
"subject": "Our autumn collection is here",
"preheader": "Twelve new pieces, made in small batches.",
"from_name": "Northwind Traders",
"from_local": "news",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": "hello@northwind-local.test",
"body": "",
"audience_tags": [
"vip"
],
"segment": null,
"topic_id": "bea753b0-6132-4bc2-bc2a-18ff86ed0079",
"experiment": null,
"experiment_result": null,
"brief": {},
"updated_by": "key sa1pteyz44qbm",
"state": "draft",
"state_reason": null,
"scheduled_at": null,
"started_at": null,
"finished_at": null,
"created_at": "2026-10-30T06:14:34.949Z",
"updated_at": "2026-10-30T06:15:20.041Z"
}/campaigns/{id}/duplicateA new draft with the same design, settings and audience, named "<name> (copy)", for the next send. Answers with its id. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/campaigns/bc95b28b-90e9-473c-93a4-bf8d79e4f3f6/duplicate \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "bc95b28b-90e9-473c-93a4-bf8d79e4f3f6"
}/campaigns/{id}/reportWhat happened to a campaign: each measure with its definition and rate, clicks by link, a timeline, the subject test's result, progress, and plain observations comparing it with your usual results. Needs audience:read.
curl https://app.rykonmail.com/api/v1/campaigns/9e210b02-8326-4122-a449-2196ccd8cc38/report \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"observations": [
"Nothing has been delivered yet, so there is nothing to compare."
],
"test": null,
"metrics": [
{
"key": "recipients",
"label": "Recipients",
"value": 2,
"rate": null,
"of": null,
"definition": "People who were eligible when sending started: subscribed, not suppressed, matching the audience."
},
{
"key": "submitted",
"label": "Sent",
"value": 2,
"rate": 1,
"of": "recipients",
"definition": "Messages accepted by our sending service. Not yet a delivery."
}
],
"links": [],
"timeline": [],
"progress": {
"queued": 0,
"skipped": 0,
"failed": 0,
"firstSent": "2026-10-30T03:34:46.270Z",
"lastSent": "2026-10-30T03:34:46.274Z"
}
}/campaigns/{id}/versionsThe campaign's saved versions, newest first (up to 100), with who made each and which were approved. Needs audience:read.
curl https://app.rykonmail.com/api/v1/campaigns/1468cf84-2fb5-4a40-97c0-6edfb1926491/versions \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"versions": [
{
"id": "1468cf84-2fb5-4a40-97c0-6edfb1926491",
"number": 1,
"source": "approval",
"note": "approved",
"subject": "Our autumn collection is here",
"content_hash": "e1abd5b754f627e8d73e9727f1199fbab6a60d699c0dcf15dc79e57cd907aa7c",
"created_by": "key sa1pteyz44qbm",
"created_at": "2026-10-30T06:15:03.264Z",
"approved": true
}
]
}/automationsThe account's automations, newest first, with how many steps each has, how many people are in it now, how many have started it and how many emails it sent. Needs audience:read.
curl https://app.rykonmail.com/api/v1/automations \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"automations": [
{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"trigger_form": "API docs example",
"trigger_tag": null,
"active": false,
"from_name": "Northwind Traders",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.698Z",
"emails": 3,
"running": 0,
"started": 0,
"sent": 0
}
]
}/automationsCreates an automation, switched off, that starts with one empty welcome email sent as soon as someone subscribes, from your first domain that is set up for sending. Fill it in with PUT /automations/:id. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/automations \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example"
}'{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": null,
"trigger_form": null,
"trigger_tag": null,
"active": false,
"from_name": "",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.563Z",
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"position": 0,
"kind": "email",
"condition": "always",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 0,
"subject": "",
"preheader": "",
"body": "",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
}
],
"runs": {}
}/automations/{id}One automation, with every step and its results (sent, delivered, opened, clicked, skipped and so on) and its people by state. Needs audience:read.
curl https://app.rykonmail.com/api/v1/automations/77fcfb0a-d16e-4046-b3b1-cc1828553111 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"trigger_form": "API docs example",
"trigger_tag": null,
"active": false,
"from_name": "Northwind Traders",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.698Z",
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"position": 0,
"kind": "email",
"condition": "always",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 0,
"subject": "Welcome to Northwind Traders",
"preheader": "",
"body": "Hi {{ name | there }},\n\nThanks for subscribing. Here is what to expect: one letter a month, never more.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
},
{
"id": "17b3a766-54af-4fd5-b54b-54f6a552cad1",
"position": 1,
"kind": "email",
"condition": "not_opened",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 4320,
"subject": "In case you missed it",
"preheader": "",
"body": "Hi {{ name | there }},\n\nA quick reminder of what is new this month.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
}
],
"runs": {}
}/automations/{id}Saves the settings and the whole sequence of steps; send them all. A step is an email, a handover to a person (notify), a tag change, a field change or the end, each after a wait, and may depend on whether the previous email was opened or clicked or on a tag. Send a step's id to keep it (and its history) when steps move. An email that has already been sent cannot be removed (409). A handover goes only to someone on the account or an address at one of its domains (400). The trigger form and the domain must be yours (400). People part way through carry on from the step they were going to do next. Needs audience:write.
curl -X PUT https://app.rykonmail.com/api/v1/automations/73606f24-166c-446c-9b55-94278a975061 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example",
"triggerFormId": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"triggerTag": null,
"fromName": "Northwind Traders",
"fromLocal": "hello",
"domainId": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"replyTo": null,
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"kind": "email",
"delayMinutes": 0,
"subject": "Welcome to Northwind Traders",
"body": "Hi {{ name | there }},\n\nThanks for subscribing. Here is what to expect: one letter a month, never more."
},
{
"kind": "email",
"condition": "not_opened",
"delayMinutes": 4320,
"subject": "In case you missed it",
"body": "Hi {{ name | there }},\n\nA quick reminder of what is new this month."
},
{
"kind": "tag",
"delayMinutes": 0,
"tagsAdd": [
"onboarded"
]
}
]
}'{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"trigger_form": "API docs example",
"trigger_tag": null,
"active": false,
"from_name": "Northwind Traders",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.637Z",
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"position": 0,
"kind": "email",
"condition": "always",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 0,
"subject": "Welcome to Northwind Traders",
"preheader": "",
"body": "Hi {{ name | there }},\n\nThanks for subscribing. Here is what to expect: one letter a month, never more.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
},
{
"id": "17b3a766-54af-4fd5-b54b-54f6a552cad1",
"position": 1,
"kind": "email",
"condition": "not_opened",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 4320,
"subject": "In case you missed it",
"preheader": "",
"body": "Hi {{ name | there }},\n\nA quick reminder of what is new this month.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
}
],
"runs": {}
}/automations/{id}Deletes an automation nobody has been through. Once people have, its history is kept and this answers 409; pause it instead. Needs audience:write.
curl -X DELETE https://app.rykonmail.com/api/v1/automations/77fcfb0a-d16e-4046-b3b1-cc1828553111 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"ok": true
}/automations/{id}/activateSwitches it on for people who subscribe from now; those already on the list are not mailed. It needs a sending domain that is set up (409), the account's postal address (400), a subject and body on every email and someone to tell on every handover (400). Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/automations/77fcfb0a-d16e-4046-b3b1-cc1828553111/activate \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"trigger_form": "API docs example",
"trigger_tag": null,
"active": true,
"from_name": "Northwind Traders",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.681Z",
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"position": 0,
"kind": "email",
"condition": "always",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 0,
"subject": "Welcome to Northwind Traders",
"preheader": "",
"body": "Hi {{ name | there }},\n\nThanks for subscribing. Here is what to expect: one letter a month, never more.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
},
{
"id": "17b3a766-54af-4fd5-b54b-54f6a552cad1",
"position": 1,
"kind": "email",
"condition": "not_opened",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 4320,
"subject": "In case you missed it",
"preheader": "",
"body": "Hi {{ name | there }},\n\nA quick reminder of what is new this month.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
}
],
"runs": {}
}/automations/{id}/pauseNobody new starts, and nobody already in it is sent anything until it is switched on again. Needs audience:write.
curl -X POST https://app.rykonmail.com/api/v1/automations/77fcfb0a-d16e-4046-b3b1-cc1828553111/pause \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "77fcfb0a-d16e-4046-b3b1-cc1828553111",
"name": "API docs example",
"trigger_form_id": "a5f0dd2d-bfc1-4247-84fa-6a8ff0dd0740",
"trigger_form": "API docs example",
"trigger_tag": null,
"active": false,
"from_name": "Northwind Traders",
"from_local": "hello",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"domain": "northwind-local.test",
"reply_to": null,
"created_at": "2026-10-30T06:15:53.563Z",
"updated_at": "2026-10-30T06:15:53.698Z",
"steps": [
{
"id": "73606f24-166c-446c-9b55-94278a975061",
"position": 0,
"kind": "email",
"condition": "always",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 0,
"subject": "Welcome to Northwind Traders",
"preheader": "",
"body": "Hi {{ name | there }},\n\nThanks for subscribing. Here is what to expect: one letter a month, never more.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
},
{
"id": "17b3a766-54af-4fd5-b54b-54f6a552cad1",
"position": 1,
"kind": "email",
"condition": "not_opened",
"condition_value": null,
"tags_add": [],
"tags_remove": [],
"field_key": null,
"field_value": null,
"notify_to": null,
"delay_minutes": 4320,
"subject": "In case you missed it",
"preheader": "",
"body": "Hi {{ name | there }},\n\nA quick reminder of what is new this month.",
"sent": 0,
"delivered": 0,
"bounced": 0,
"complained": 0,
"failed": 0,
"skipped": 0,
"opened": 0,
"clicked": 0
}
],
"runs": {}
}/automations/{id}/runsEach person's progress, newest first (up to 200): where they are, when their next step is due, what each step did, or why they stopped. Needs audience:read.
curl "https://app.rykonmail.com/api/v1/automations/d94cbb6d-481e-46e7-bd38-3fb48b6e643f/runs?state=completed" \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"runs": [
{
"id": "d94cbb6d-481e-46e7-bd38-3fb48b6e643f",
"email": "riley.1793252140@reader-local.test",
"name": "Riley Chen",
"state": "completed",
"next_position": 2,
"next_at": "2026-10-29T05:42:17.604Z",
"exit_reason": null,
"waiting_reason": null,
"started_at": "2026-10-29T05:36:05.795Z",
"finished_at": "2026-10-29T05:37:17.637Z",
"sends": [
{
"position": 0,
"state": "sent",
"sentAt": "2026-10-29T05:36:16.998971+00:00",
"error": null
},
{
"position": 1,
"state": "skipped",
"sentAt": "2026-10-29T05:36:47.308649+00:00",
"error": "condition not met: not opened"
}
]
}
]
}/domainsThe account's domains, newest first, with their state, whether their DNS has drifted, and whether ownership is proven. Needs domains:read.
curl https://app.rykonmail.com/api/v1/domains \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"domains": [
{
"id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"tenant_id": "7024c88d-abfb-4555-bd16-530a53d2c22f",
"name": "northwind-local.test",
"state": "active",
"state_reason": null,
"dns_mode": "external",
"verified_at": null,
"observed_at": null,
"drifted": false,
"drift_since": null,
"created_at": "2026-10-29T05:14:24.823Z",
"owned": true
}
]
}/domainsAdds a domain and starts setting it up; it answers 202 with the job, because setup waits on DNS. First publish the ownership record it returns: nothing is configured for the domain until that record is seen, and then the mail records to publish appear on GET /domains/:id. Retrying joins the setup already running. A domain already on your account or registered on the platform answers 409, as does one this platform runs on. Your plan sets how many domains you can have (409 beyond that). Needs domains:write.
curl -X POST https://app.rykonmail.com/api/v1/domains \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "api-docs-example.test"
}'{
"id": "a463c960-1309-4174-b629-b03ff1d6ba1c",
"domain": "api-docs-example.test",
"state": "ownership_pending",
"ownership": {
"kind": "TXT",
"name": "_rykonmail.api-docs-example.test",
"value": "rykonmail-verification=fwvhweev823wa8j4tmq4fm6yzd5vm8ye",
"purpose": "Proves this domain is yours. Nothing is set up until it is seen.",
"verified": false,
"verifiedAt": null
},
"job": {
"id": "40b62db3-14f6-466d-905a-ee2d9218c38c",
"state": "queued"
},
"next": "Publish the ownership record. Nothing is configured for this domain until it is seen, and then the mail records follow."
}/domains/{id}One domain: the ownership record, the records to publish and what was last seen of each, the records still outstanding, the sending identity, where its mailboxes connect, its recent setup jobs and a plain summary of where it stands. Needs domains:read.
curl https://app.rykonmail.com/api/v1/domains/a463c960-1309-4174-b629-b03ff1d6ba1c \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "a463c960-1309-4174-b629-b03ff1d6ba1c",
"tenant_id": "7024c88d-abfb-4555-bd16-530a53d2c22f",
"name": "api-docs-example.test",
"state": "ownership_pending",
"state_reason": null,
"dns_mode": "external",
"desired_records": [],
"observed_records": [],
"observed_at": null,
"verified_at": null,
"drift": null,
"drift_since": null,
"ownership_verified_at": null,
"created_at": "2026-10-30T06:16:36.079Z",
"ownership": {
"kind": "TXT",
"name": "_rykonmail.api-docs-example.test",
"value": "rykonmail-verification=fwvhweev823wa8j4tmq4fm6yzd5vm8ye",
"purpose": "Proves this domain is yours. Nothing is set up until it is seen.",
"verified": false,
"verifiedAt": null
},
"mailHost": null,
"webmailUrl": "https://mail.rykonmail.com/",
"sendingIdentity": null,
"outstanding": [],
"summary": "Waiting for the ownership record.",
"jobs": [
{
"id": "40b62db3-14f6-466d-905a-ee2d9218c38c",
"operation": "domain.provision",
"state": "queued",
"wait_reason": null,
"failure_reason": null,
"attempts": 0,
"needs_operator": false,
"created_at": "2026-10-30T06:16:36.079Z",
"finished_at": null
}
]
}/domains/{id}/verifyChecks the domain's DNS now instead of waiting for the next sweep, and answers 202 with the job. While setup is still running it is brought forward ("nudged"); setup that stopped is started again ("retried"), at most once a minute (409 sooner). Needs domains:write.
curl -X POST https://app.rykonmail.com/api/v1/domains/40b62db3-14f6-466d-905a-ee2d9218c38c/verify \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"job": {
"id": "40b62db3-14f6-466d-905a-ee2d9218c38c"
},
"nudged": true
}/domains/{id}/setupWho hosts the domain's DNS, whether a DNS provider is connected, and whether the automatic ways to publish the records (a Cloudflare token, Domain Connect) are open for it. It asks the domain's nameservers, so fetch it once rather than polling. Needs domains:read.
curl https://app.rykonmail.com/api/v1/domains/965a5cd1-455b-4c7d-8ae3-f68caf0c7993/setup \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"zone": "northwind-local.test",
"nameservers": [],
"provider": null,
"recordsReady": false,
"connection": null,
"cloudflare": {
"available": true,
"reason": null
},
"domainConnect": {
"available": false,
"provider": null,
"reason": "Domain Connect is off on simulated backends: it would publish records the simulation made up."
},
"simulated": true
}/domains/{id}Removes a domain from your account and from every backend: the mail store, the sending identity and the relay, so mail to it no longer arrives here and nothing is sent from it. Because that cannot be undone, the body must name the domain in confirm (400 if it is missing, 409 if it does not match). Refused with 409 while the domain still has mailboxes (the message names up to five; delete them first, since each one holds mail) or while a campaign is scheduled, sending or paused from it. In the console only the owner or an admin may remove a domain. Needs domains:write.
curl -X DELETE https://app.rykonmail.com/api/v1/domains/965a5cd1-455b-4c7d-8ae3-f68caf0c7993 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"confirm": "example.com"
}'{
"removed": true,
"name": "example.com"
}/mailboxesThe account's mailboxes, by address, with their quota, use, where they connect and which belong to an agent. Needs mailboxes:read.
curl "https://app.rykonmail.com/api/v1/mailboxes?domainId=965a5cd1-455b-4c7d-8ae3-f68caf0c7993" \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"mailboxes": [
{
"id": "7854885b-10e0-4dee-a7df-88351b9ae155",
"address": "docs.example@northwind-local.test",
"display_name": "API docs example",
"quota_mb": 10240,
"state": "active",
"used_bytes": null,
"message_count": null,
"usage_at": null,
"state_reason": null,
"created_at": "2026-10-30T06:16:53.907Z",
"domain": "northwind-local.test",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"mail_host": "mail.rykontech.com",
"webmail_url": "https://mail.rykonmail.com/",
"agent_id": null
}
]
}/mailboxesCreates a mailbox and answers 202 while it is made. It has no password until you issue one with POST /mailboxes/:id/password. The domain must be verified and its sending set up (409 until then, usually a minute or two after verifying). An address that exists answers 409, as does a quota above your plan's largest mailbox or a plan with no room for another. Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/mailboxes \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"domainId": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"localPart": "docs.example",
"displayName": "API docs example"
}'{
"id": "7854885b-10e0-4dee-a7df-88351b9ae155",
"address": "docs.example@northwind-local.test",
"state": "requested",
"job": {
"id": "493ab353-4e03-4b77-b451-a006ac0b2903"
},
"next": "The mailbox is being created. It has no password until one is issued through POST /v1/mailboxes/:id/password."
}/mailboxes/{id}/passwordIssues a new password and returns it in this response only: it is never stored and cannot be read back, so a lost one is replaced, not recovered. The old password stops working. Only an active mailbox can have one (409), and an agent's mailbox is managed through its agent (409). Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/mailboxes/7854885b-10e0-4dee-a7df-88351b9ae155/password \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"address": "docs.example@northwind-local.test",
"password": "w3we...",
"shownOnce": "This password is shown once and is not stored. Issue a new one if it is lost."
}/mailboxes/{id}Deletes a mailbox and every message in it. Because that cannot be undone, the body must name the address in confirm (400 if it is missing, 409 if it does not match). A mailbox that belongs to an agent, or that a shared address delivers to, answers 409 until that is changed. Needs mailboxes:write.
curl -X DELETE https://app.rykonmail.com/api/v1/mailboxes/7854885b-10e0-4dee-a7df-88351b9ae155 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"confirm": "docs.example@northwind-local.test"
}'{
"removed": true,
"address": "docs.example@northwind-local.test"
}/aliasesThe account's shared addresses, by address, with the mailboxes each delivers to. Needs mailboxes:read.
curl https://app.rykonmail.com/api/v1/aliases \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"aliases": [
{
"id": "b9af2939-fc53-43cc-821b-07b180fae413",
"address": "docs-sales@northwind-local.test",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"created_at": "2026-10-30T06:17:15.298Z",
"targets": [
{
"id": "7854885b-10e0-4dee-a7df-88351b9ae155",
"address": "docs.example@northwind-local.test",
"name": "API docs example"
}
]
}
]
}/aliasesMakes an address like sales@ that delivers to one or more of your own mailboxes, an agent's included. Only your own active mailboxes can receive (400 otherwise); forwarding outside the account is not offered. The domain must be verified (409). An address that is already a mailbox or a shared address answers 409, as does a plan with no room for another. Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/aliases \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"domainId": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"localPart": "docs-sales",
"targets": [
"7854885b-10e0-4dee-a7df-88351b9ae155",
"14c2cc36-e349-419b-9bf6-4193fed78d1d"
]
}'{
"id": "b9af2939-fc53-43cc-821b-07b180fae413",
"address": "docs-sales@northwind-local.test",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"created_at": "2026-10-30T06:17:15.298Z",
"targets": [
{
"id": "14c2cc36-e349-419b-9bf6-4193fed78d1d",
"address": "docs.agent@northwind-local.test",
"name": "API docs example"
},
{
"id": "7854885b-10e0-4dee-a7df-88351b9ae155",
"address": "docs.example@northwind-local.test",
"name": "API docs example"
}
]
}/aliases/{id}Replaces the mailboxes a shared address delivers to. Only your own active mailboxes can receive (400 otherwise). Needs mailboxes:write.
curl -X PUT https://app.rykonmail.com/api/v1/aliases/b9af2939-fc53-43cc-821b-07b180fae413 \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"targets": [
"7854885b-10e0-4dee-a7df-88351b9ae155"
]
}'{
"id": "b9af2939-fc53-43cc-821b-07b180fae413",
"address": "docs-sales@northwind-local.test",
"domain_id": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"created_at": "2026-10-30T06:17:15.298Z",
"targets": [
{
"id": "7854885b-10e0-4dee-a7df-88351b9ae155",
"address": "docs.example@northwind-local.test",
"name": "API docs example"
}
]
}/aliases/{id}Deletes a shared address; mail to it is no longer delivered. The mailboxes it delivered to are not touched. Needs mailboxes:write.
curl -X DELETE https://app.rykonmail.com/api/v1/aliases/b9af2939-fc53-43cc-821b-07b180fae413 \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"ok": true
}/agentsThe account's AI agents with their mailbox, what they may send and to whom, and what they did in the last 24 hours. Needs mailboxes:read.
curl https://app.rykonmail.com/api/v1/agents \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"agents": [
{
"id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"name": "API docs example",
"state": "active",
"address": "docs.agent@northwind-local.test",
"send_to": "listed",
"allow_list": [
"example.com"
],
"daily_limit": 20,
"approve_all": true,
"created_at": "2026-10-30T06:16:53.930Z",
"on_new_mail": "nothing",
"notify_url": null,
"notify_secret_set": false,
"reply_instructions": "",
"watched_at": null,
"today": {
"agent_id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"sent": 1,
"read": 0,
"pending": 0
}
}
]
}/agentsGives an AI agent a mailbox of its own and answers while it is set up (state "provisioning"). Its mailbox counts against your plan. The domain must be verified and its sending set up (409 until then), and an address that exists answers 409. By default it may write only to people who wrote to it first, 50 a day; change that with PUT /agents/:id. Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/agents \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example",
"domainId": "965a5cd1-455b-4c7d-8ae3-f68caf0c7993",
"localPart": "docs.agent"
}'{
"id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"name": "API docs example",
"state": "provisioning",
"address": "docs.agent@northwind-local.test",
"send_to": "repliers",
"allow_list": [],
"daily_limit": 50,
"approve_all": false,
"created_at": "2026-10-30T06:16:53.930Z",
"on_new_mail": "nothing",
"notify_url": null,
"notify_secret_set": false,
"reply_instructions": "",
"watched_at": null,
"keys": [],
"today": {
"agent_id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"sent": 0,
"read": 0,
"pending": 0
}
}/agents/approvalsEvery message an agent wrote that waits for a person, across the account, oldest first, with why it was held. Needs mailboxes:read.
curl https://app.rykonmail.com/api/v1/agents/approvals \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"waiting": [
{
"id": "9d60d5d5-5d40-4556-98d5-cf44f59c3a31",
"agent_id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"agent": "API docs example",
"agent_address": "docs.agent@northwind-local.test",
"recipients": [
"sam.lee@example.com"
],
"cc": [],
"subject": "Your order 1",
"body": "Hi Sam,\n\nYour order is ready to collect from Friday.\n\nAPI docs example",
"held_because": "every message from this agent waits for a person",
"error": null,
"created_at": "2026-10-30T06:17:35.433Z"
}
]
}/agents/outbox/{outboxId}/approveSends a held message, as the agent wrote it or with your edits to the subject and body. Sent once however many times this is called: a message already decided answers 404, and one from a paused agent answers 409 and stays waiting. Answers whether it was sent. Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/agents/outbox/OUTBOX_ID/approve \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "Your order is ready",
"body": "Hi Sam,\n\nYour order is ready to collect from Friday, 9 to 5.\n\nNorthwind Traders"
}'{
"state": "sent",
"messageId": "<f7101765-c6c1-4999-9042-6fb8501a6ed8@northwind-local.test>"
}/agents/outbox/{outboxId}/rejectRejects a held message, which is then never sent; the reason is kept in the agent's record. A message already decided answers 404. Needs mailboxes:write.
curl -X POST https://app.rykonmail.com/api/v1/agents/outbox/OUTBOX_ID/reject \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Sent the first one already."
}'{
"state": "rejected"
}/agents/{id}One agent, with its sending rules, its keys (never their secrets) and what it did in the last 24 hours. Needs mailboxes:read.
curl https://app.rykonmail.com/api/v1/agents/8ca997e9-6ff5-49bc-ba30-a0933f00012a \
-H "Authorization: Bearer $RYKON_MAIL_KEY"{
"id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"name": "API docs example",
"state": "active",
"address": "docs.agent@northwind-local.test",
"send_to": "listed",
"allow_list": [
"example.com"
],
"daily_limit": 20,
"approve_all": true,
"created_at": "2026-10-30T06:16:53.930Z",
"on_new_mail": "nothing",
"notify_url": null,
"notify_secret_set": false,
"reply_instructions": "",
"watched_at": null,
"keys": [
{
"id": "49b887b6-3473-4d17-b39c-1cc56283ea4b",
"prefix": "eqrf9c76cx0hy",
"name": "API docs capture",
"created_at": "2026-10-30T06:17:26.696Z",
"created_by": "key sa1pteyz44qbm",
"last_used_at": "2026-10-30T06:17:35.452Z"
}
],
"today": {
"agent_id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"sent": 1,
"read": 0,
"pending": 0
}
}/agents/{id}Decides what an agent may send without a person: always to colleagues at its own domain, then to anyone, only people who wrote to it first, or an allow list, within a daily limit; with approveAll everything waits for a person. Send every field. Needs mailboxes:write.
curl -X PUT https://app.rykonmail.com/api/v1/agents/8ca997e9-6ff5-49bc-ba30-a0933f00012a \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "API docs example",
"sendTo": "listed",
"allowList": [
"example.com"
],
"dailyLimit": 20,
"approveAll": true
}'{
"id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"name": "API docs example",
"state": "active",
"address": "docs.agent@northwind-local.test",
"send_to": "listed",
"allow_list": [
"example.com"
],
"daily_limit": 20,
"approve_all": true,
"created_at": "2026-10-30T06:16:53.930Z",
"on_new_mail": "nothing",
"notify_url": null,
"notify_secret_set": false,
"reply_instructions": "",
"watched_at": null,
"keys": [],
"today": {
"agent_id": "8ca997e9-6ff5-49bc-ba30-a0933f00012a",
"sent": 0,
"read": 0,
"pending": 0
}
}/agents/{id}Removes an AI agent. Its keys are revoked at once, every message it has waiting for approval is rejected, and its mailbox and every message in it are deleted. Because that cannot be undone, the body must name the agent's address in confirm (400 if it is missing, 409 if it does not match). In the console only the owner or an admin may remove an agent. Needs mailboxes:write.
curl -X DELETE https://app.rykonmail.com/api/v1/agents/8ca997e9-6ff5-49bc-ba30-a0933f00012a \
-H "Authorization: Bearer $RYKON_MAIL_KEY" \
-H "Content-Type: application/json" \
-d '{
"confirm": "helper@example.com"
}'{
"removed": true,
"address": "helper@example.com"
}