Guides
Contacts and consent
Your audience is the people who agreed to hear from you. Add them from your app with the consent they gave, recorded word for word, and Rykon Mail keeps their own choices from then on.
Consent comes with every contact
A contact added through the API carries a consent object, and it is required:
text: what the person agreed to, in the words they saw, 10 to 2,000 characters. Required.source: where they agreed, such as "Checkout opt-in".APIif you leave it out.ip: the address they agreed from, if you have it.
It is the record you show when someone asks why they got an email, so send the real wording of your checkbox or form, not a summary.
Double opt-in
With "doubleOptIn": true 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.
Syncing safely
- Adding someone who is already subscribed fills them in rather than replacing them: a name if given, new tags and attributes merged, nothing taken away. That answers
200with"created": false; a new contact answers201. So your sync can send everyone, every time. - Some things only the person can do. An address that unsubscribed or asked to be erased comes back only through their own confirmation, so adding it answers
409unless you ask for double opt-in. - An address that bounced or marked your mail as spam can never be added again (
409). - When someone asks you directly to stop, unsubscribe them on their behalf. They go on your suppression list and leave any automation they are in.
/contactsAdd a contact
Adds 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.
Body
- emailstring (email)required
- Up to 320 characters; stored in lower case.
- namestringoptional
- Up to 200 characters.
- tagsstring[]optional
- Up to 20 tags of up to 40 characters each.
- attributesobjectoptional
- Up to 50 fields of your own: keys up to 60 characters, values a string (up to 500), number, boolean or null.
- consentobjectrequired
- What the person agreed to: text (10 to 2,000 characters, in the words they saw, required), source (where they agreed, up to 120 characters; "API" if left out) and ip (optional).
- doubleOptInbooleanoptional
- Send a confirmation email and keep the contact pending until it is used. false if left out.
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
}In the reference: Add a contact
Importing a list
An upload is never consent, so an import asks you to declare where these people came from. With basis subscribed you state that they agreed, and your consentText is recorded on every contact created. With unconfirmed they stay pending, and pending contacts are never sent marketing.
People already on your list keep their status; an import only adds tags and fills empty fields. Unsubscribed, bounced and complained addresses are skipped. Preview the file first to see how its columns will be read, and undo any import if it was the wrong file.
Only people who said yes
subscribed only for people who really agreed to hear from you. Mail to people who did not is what lands a sender in spam, and the declaration is yours./contacts/importImport contacts from CSV
Imports 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.
Body
- csvstringrequired
- The file's contents, with a header row.
- mappingstring[]required
- What each column holds: email, name, first_name, last_name, tags, ignore or field:<key>.
- basis'subscribed' | 'unconfirmed'required
- Whether these people agreed to hear from you, by your declaration, or still need to confirm.
- consentTextstringrequired
- 20 to 1,000 characters: where these people came from and how they agreed.
- declarationbooleanrequired
- Must be true: you confirm the declaration above.
- fileNamestringoptional
- The file's name, for the import's record.
- tagsstring[]optional
- Up to 10 tags to add to everyone in the file: lower case letters, numbers and dashes.
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"
}In the reference: Import contacts from CSV
Every contact endpoint
- GET
/contactsList contacts - POST
/contacts/import/previewPreview a CSV import - GET
/contacts/importsList imports - POST
/contacts/imports/{id}/undoUndo an import - GET
/contacts/{id}Get a contact - PATCH
/contacts/{id}Update a contact - POST
/contacts/{id}/unsubscribeUnsubscribe a contact - DEL
/contacts/{id}Erase a contact - GET
/contacts/{id}/topicsList a contact's topics - PUT
/contacts/{id}/topics/{topicId}Set a contact's topic choice
Topics
Topics are what people can subscribe to, so each one hears only about what they asked for. Set a contact's topics from your preference centre with the contact endpoints above.
- GET
/topicsList topics - POST
/topicsCreate a topic - PATCH
/topics/{id}Update a topic - DEL
/topics/{id}Archive a topic
Sign-up forms
A Rykon Mail form records consent itself, in the words the form shows. Create and configure forms here, and check that one is live on your site.