Collect newsletter sign-ups and manage unsubscribes
Add a newsletter form to your storefront, record who agreed to marketing email, honour unsubscribe links and export the people you may mail.
In the dashboard
A lead is an address a visitor gives your storefront's newsletter form. A suppression is an address that gets no marketing email from your organization. This page covers the form, how customers agree to marketing email, the unsubscribe link in every marketing email, the suppression list and the export of everyone you may mail.
Marketing email is email a customer did not ask for about their order or service: the newsletter, offers, onboarding messages and requests for a review. It goes only to someone who agreed to it and is not on your suppression list. Email about a customer's own orders and services, such as an unpaid invoice, needs no agreement. The suppression list still stops it when the address is on it.
Before you begin
Section titled Before you begin- You need the Owner or Admin role to read or change anything on this page. Your team uses the routes under
/staff/marketing/subscribersin the staff console, under Marketing, and an owner can call the same routes under/subscribers. The newsletter form and the unsubscribe link need no sign-in. - The newsletter form is off until you turn it on. Send
PUT /api/v1/orgs/{org_slug}/subscribers/settingswith{"enabled": true}. While it is off, the form's route answers404. - Your storefront has to serve the page that the unsubscribe link opens. See Unsubscribe links.
Newsletter settings
Section titled Newsletter settingsGET /subscribers/settings reads these keys and PUT changes the ones you send:
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Whether the form accepts sign-ups. |
turnstile |
true |
Whether the form asks for a Turnstile check. It applies when your brand has Turnstile keys, as described in Sign customers in to your storefront. |
consent_text |
Send me news and offers by email. I can unsubscribe at any time. |
The words beside the form's checkbox. We store them as the visitor's agreement when the form sends no words of its own. At most 300 characters. An empty text goes back to the default. |
A longer consent_text answers 422 with detail and field. A save that changes nothing writes no audit entry, and a change is audited as subscribers.settings_updated.
Set it up in your dashboard
Section titled Set it up in your dashboardOwners and admins run the newsletter form in the dashboard. Your team can do the same in the staff console, as Marketing audience in the staff console describes. A member with another role does not see the page, and its address opens Overview.
- In the dashboard, open the organization, then Growth, then Subscribers.
- Under Newsletter form, turn on Accept sign-ups. Turn on Check visitors with Turnstile to ask for a check before a sign-up counts. It applies once your storefront has Turnstile keys.
- Change "Words beside the checkbox" if you want words of your own. The page counts the characters, and 300 is the most. Emptying the field brings back the default words, which the card shows under it.
- Select Save changes. The page says "Newsletter form turned on." when the switch was the only change, and "Newsletter settings saved." otherwise. A value the API refuses shows its sentence under the field.
- Read the three figures under the card: Leads, Consenting customers and Suppressed.
When it works, the badge on the card reads On and a visitor who signs up appears under Leads.
Leads lists the addresses newest first. Each row shows its source, Agreed or Did not agree, a Suppressed badge when the address is on the suppression list, and a Customer badge, with the address linking to the customer, when it belongs to one. Search by address, choose "All leads", "Agreed to email" or "Did not agree", or type a source. The search, the filters and the page stay in the address. A row shows the words the person agreed to only when they differ from the card's current words. To delete a lead, open its actions menu and select Delete lead…. Only the lead goes: the customer, their agreement and the suppression list stay as they are.
Select Export CSV to save the people you may mail: leads who agreed and customers who agreed to marketing email, and never an address on the suppression list. A file holds at most 100,000 addresses. You can export 12 times an hour, and the page names the wait when you reach the limit.
Suppression list shows each address with why it is on the list ("Unsubscribed", "Bounced", "Marked as spam" or "Added by hand") and how it got there. Select Add address…, type the email address and select Add address to stop all marketing email to it. The reason is always "Added by hand", and an address already on the list is left as it is. To take an address off, open its actions menu and select Take off the list…. That never gives anyone's agreement back, so a person who withdrew still gets no marketing email until they agree again.
Add the form to your storefront
Section titled Add the form to your storefrontPost the visitor's address to POST /api/v1/orgs/{org_slug}/storefront/newsletter:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/newsletter" \
-H "Content-Type: application/json" \
-d '{"email": "alex@example.com", "source": "footer", "consent_text": "Send me news and offers by email. I can unsubscribe at any time.", "website": "", "turnstile_token": "..."}'
The answer is 202 with:
{ "ok": true }
| Field | Required | Meaning |
|---|---|---|
email |
yes | The address. It must be one mail can reach: an address on a .invalid domain, or one over 254 characters, answers 422 with field set to email. |
source |
no | Where the form is, such as footer or checkout. At most 64 characters, with control characters taken out. It defaults to newsletter. |
consent_text |
no | The words the visitor agreed to, as your form showed them. At most 500 characters. Left out, we store the consent_text setting. |
website |
no | A field no person sees. Leave it empty. A request that fills it answers 202 and stores nothing. |
turnstile_token |
when the check is on | The Cloudflare Turnstile answer. A missing or failed one answers 403 with detail.error set to turnstile_failed, before anything is stored. |
What the answer never says
Section titled What the answer never saysThe answer is the same 202 {"ok": true} for a new address, an address that signed up before, an address of one of your customers and an address on your suppression list. No answer holds an address. A visitor cannot use the form to find out who is a customer or who unsubscribed.
What a sign-up stores
Section titled What a sign-up stores- A lead with the address in lower case, the
source,consentset totrue, the words the visitor agreed to, the visitor's IP address and the time. - A second sign-up with the same address keeps the first time and changes only what it sends:
sourceandconsent_textwhen the form sends them, and the IP address. - A customer of your organization with the same address is linked to the lead and agrees to marketing email, with the source
newsletter. A customer of another organization with that address is not touched. - An address on your suppression list stays on it. The form never removes a suppression. We keep the lead with
consentset tofalseand we leave the consent of a customer with that address as it was.
Limits
Section titled Limits| Limit | Value |
|---|---|
| Sign-ups from one visitor address | 10 an hour |
| Sign-ups for your organization | 2,000 an hour |
Past either limit the answer is 429 with Retry-After, and nothing is stored.
How customers agree to marketing email
Section titled How customers agree to marketing emailEvery customer account starts with marketing_consent set to false. A customer says yes in one of three places:
| Where | Source stored | How |
|---|---|---|
| Sign-up | signup |
marketing_consent on POST /auth/register. It is false unless your form sends true for a ticked checkbox. |
| Their account | account |
marketing_consent on PATCH /auth/me. Sending true also takes their address off your suppression list, because they asked for the email from inside their own session. |
| The newsletter form | newsletter |
A sign-up with their address, unless the address is suppressed. |
We store the answer with the time it was given. A repeat of the answer already stored changes nothing, so the time and the source keep saying when the customer first agreed. GET /auth/me returns marketing_consent, marketing_consent_at and marketing_consent_source. See Sign customers in to your storefront.
A customer whose address is on your suppression list can still register with marketing_consent set to true. We store the answer as sent, and the suppression still stops the email.
When a customer closes their account, we delete the leads with their address and keep the suppression, so a later sign-up with the same address is still not mailed. The consent columns stay as they were. The customer's data export has a Marketing section with their consent, their lead rows and whether their address is suppressed.
Unsubscribe links
Section titled Unsubscribe linksEvery marketing email carries a link in its unsubscribe_url variable:
https://shop.example.com/unsubscribe?t=<token>
The token names your organization and the address, and it never expires, so an old email still unsubscribes. Your storefront serves the page at /unsubscribe, reads t from its address and calls the API with it:
GET /api/v1/orgs/{org_slug}/storefront/unsubscribe?t=<token>answers what the page shows and changes nothing, because mail programs and link scanners open links with GET:JSON{ "unsubscribed": false, "email_hint": "a***@example.com", "brand_name": "Acme Hosting" }email_hintkeeps the first character of the address and the domain, and hides the rest.POST /api/v1/orgs/{org_slug}/storefront/unsubscribe?t=<token>puts the address on your suppression list and answers{"ok": true}. The reason isunsubscribedand the source islink. Doing it twice changes nothing more.
A token that is wrong, damaged or made for another organization answers 404 with This link is not valid., the same answer as an unknown organization. At most 120 POST requests a minute from one visitor address.
Each marketing email also carries the List-Unsubscribe header with a one-click address on the API, and List-Unsubscribe-Post: List-Unsubscribe=One-Click. Mail programs send a POST to that address with the form List-Unsubscribe=One-Click. The route does not read the body, so the form, JSON such as {} and an empty body all work.
Adding an address to the suppression list also:
- cancels the automatic emails still waiting for that address;
- takes marketing consent back from a customer with that address (source
unsubscribe, orstafffor an entry made in the console); - marks a lead with that address as not consenting.
Suppressions
Section titled SuppressionsThe suppression list belongs to your organization. An address that another brand suppressed is not on yours, and one you suppressed is still mailable by them.
GET /subscribers/suppressions lists it, newest first, with q for part of an address, limit (1 to 200, default 50) and offset. Each row has id, email, reason and source:
| Reason | Set by |
|---|---|
unsubscribed |
A person selecting the unsubscribe link in an email, with the source link. |
manual |
Your team, with the source console. |
A row can also have the reason bounced or complained.
To stop marketing email to one address yourself, send POST /subscribers/suppressions with {"email": "alex@example.com", "reason": "manual"}. The reason must be manual, and a bad address or reason answers 422 with detail and field. An address already on the list stays as it was and the answer has added set to false. A new entry is audited as suppression.added.
DELETE /subscribers/suppressions/{suppression_id} takes an address off the list, audited as suppression.lifted. It does not give anyone's consent back: a customer or lead who withdrew has to agree again before marketing email reaches them.
Read the leads
Section titled Read the leadsGET /subscribers lists leads, newest first. Each row has id, email, source, consent, consent_text, customer_id, suppressed and created_at. Filter with q for part of an address, source and consent (true or false), and page with limit (1 to 200, default 50) and offset. total is every lead the filters match.
counts covers the whole organization:
| Count | Meaning |
|---|---|
leads |
Every lead. |
consenting_customers |
Active customers who agreed to marketing email. |
suppressed |
Addresses on the suppression list. |
Warning
DELETE /subscribers/{lead_id} deletes one lead for good. It does not change the customer with that address, their consent or the suppression list, so an address that unsubscribed stays unsubscribed. It is audited as subscriber.deleted with the lead's id and source, never its address.
Export the people you may mail
Section titled Export the people you may mailGET /subscribers/export.csv answers a CSV file with the columns email, source, consent, kind and created_at.
- The file lists leads who agreed to email, and active customers who agreed to marketing email. An address that is both is listed once, as a lead.
- It never lists an address on the suppression list, a lead who did not consent or a customer who did not.
- A cell that starts with
=,+,-or@begins with an apostrophe, so a spreadsheet shows it as text instead of running it. - A file holds at most 100,000 rows. When your list is longer, the answer has the header
X-Export-Truncated: true. - You can export 12 times an hour. After that the answer is
429. - Every export is audited as
subscribers.exportedwith the row count before the file is answered.
Result
Section titled ResultA visitor who signs up appears in GET /subscribers with consent set to true. A visitor or customer who selects the unsubscribe link appears in GET /subscribers/suppressions, and neither the export nor any marketing email includes their address from then on.
Troubleshooting
Section titled TroubleshootingThe form answers 404
Section titled The form answers 404The newsletter form is off, or the organization in the address is wrong. Turn it on with PUT /subscribers/settings and {"enabled": true}.
The form answers 403 with turnstile_failed
Section titled The form answers 403 with turnstile_failedYour brand has Turnstile keys and the request carried no valid turnstile_token. Show the Turnstile challenge on the form and send its answer, or set turnstile to false.
The form answers 429
Section titled The form answers 429One visitor address made more than 10 sign-ups in an hour, or your organization received more than 2,000. Wait for the time in Retry-After.
A customer agreed to marketing email and gets none
Section titled A customer agreed to marketing email and gets noneLook for their address on the suppression list with GET /subscribers/suppressions?q=. A suppression wins over consent. If they want the email back, they can turn marketing email on in their account, which lifts it, or you can delete the row.
An unsubscribe link answers 404
Section titled An unsubscribe link answers 404The token is wrong, damaged or from another organization. Check that your storefront passes t through unchanged and calls the API of the organization that sent the email.
What your customers see
Section titled What your customers seeA storefront that Coritan serves draws the form, the sign-up box, the account switch and the unsubscribe page for you. This section says what a visitor reads, so your support team can answer questions about them. A storefront you build yourself calls the routes above and writes its own words.
The newsletter form
Section titled The newsletter formThe form sits in the footer of every page while the form is on. It reads "Get news and offers", with an "Email address" field, a "Subscribe" button and, under them, the words from your consent_text setting. It sends the address, the source footer, the words it showed and, when the check is on, the Turnstile answer.
- The check loads when the visitor first selects the field, not when the page loads. It shows only when
turnstileis on and your brand has Turnstile keys. A visitor who presses "Subscribe" before the check has answered reads "Finish the check below, then press Subscribe." - A person never sees the
websitefield, and it cannot take focus. A script that fills it gets the same202and nothing is stored. - On success the form gives way to one line, "You are on the list." We send no confirmation email, so the line never tells the visitor to check an inbox.
- A refusal shows under the field in one sentence, and the check runs again so the next try has a fresh answer:
| What happened | What the visitor reads |
|---|---|
| The address is not shaped like an address | "Enter a valid email address." |
422 from the API |
"Enter a valid email address." or "That address cannot receive email. Check it and try again." |
403 |
"We could not confirm that you are a person. Try again." |
429 |
"Too many sign-ups right now. Try again later." |
404, because the form is off |
"Sign-ups are closed right now." |
| No answer from the server | "We could not reach the server. Check your connection and try again." |
| Any other answer | "Something went wrong. Try again." |
The form keeps its height through every answer, so nothing on the page moves.
The sign-up box
Section titled The sign-up boxThe password sign-up form has an unticked box: "Email me news and offers. I can unsubscribe at any time." The form sends its value as marketing_consent. A storefront whose customers sign in with a code has no such form, and no checkout or social sign-in asks, because the API takes no consent there. A customer on those storefronts says yes in their account.
The account switch
Section titled The account switchThe account has a "Marketing email" section with a switch, "Email me news and offers". Under it the page says "On since" or "Off since" with the date of the customer's last answer, or "You have not chosen yet." It also says what the switch covers: "Emails about your orders, invoices and account always reach you."
The switch sends PATCH /auth/me with marketing_consent, and the page then reads "Saved. You will get news and offers by email." or "Saved. You will not get news and offers by email." A save that fails leaves the switch where it was and reads "Could not save your choice. Try again." Turning the switch on also takes the customer's address off your suppression list. A support session cannot change it.
The unsubscribe page
Section titled The unsubscribe page/unsubscribe?t=<token> is the page a marketing email's link opens. It needs no sign-in, tells search engines to skip it and is not in your sitemap.
- Opening the link reads the token with
GETand changes nothing. The page shows "Unsubscribe from marketing email", the brand name, the address with its local part hidden and an "Unsubscribe" button: "You are about to stop marketing email from Acme Hosting to a***@example.com." - Only the button sends the
POST. The page then reads "You are unsubscribed" and "You will not get marketing email from Acme Hosting again." An address that is already on the list reads that line at once, with no button. - A link that is not valid, or that has no token, reads "This link is not valid" and offers "Go to your account", where the same choice is a switch.
- When the API cannot be reached the page says so and offers "Try again". A rate limit reads "Too many requests. Try again in a minute."
Every line says that email about orders, invoices and the account still comes.