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.

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". API if 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 200 with "created": false; a new contact answers 201. 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 409 unless 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.
POST/contacts

Add 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.

Needsaudience:writeReturns201 Created

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.
POST/contacts
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"
    }
  }'
Response
201 Created
{
  "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

Declare 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.
POST/contacts/import

Import 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.

Needsaudience:writeReturns201 Created

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.
POST/contacts/import
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"
    ]
  }'
Response
201 Created
{
  "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

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.

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.