Subscriptions & Consent
How subscription types, subtype consent, campaigns, and double opt-in work in ArtaMail.
{{unsubscribe_<slug>}} link.ArtaMail separates list membership (segmentation) from subscription consent (legal/opt-in state). Marketing campaigns only reach contacts who opted in to the bound marketing subtype with confirmed consent.
Concepts
Three levels — do not mix them up:
| Level | What it is | Example |
|---|---|---|
| Account | Isolated workspace — one brand/product, own contacts and API key | SuperWidget app |
Subscription type | Consent category inside an account. Drives typed unsubscribe links {{unsubscribe_<slug>}} and optional per-category preference pages. | Community & roadmap |
Subscription subtype | Atomic opt-in — what campaigns, API opt_in_subtype_slugs, and preference saves actually use. Channel: marketing, operational, or transactional. | platform-events |
Contact lists | Segmentation only — joining a list does not grant consent | VIP customers list |
contacts.subscribed | Master switch — blocks all marketing campaigns when false (admin or SES complaint) | — |
One type or several? (within one account)
Multiple brands → separate accounts, not extra subscription types. Types are for consent categories inside one product.
| Your situation | Recommended model |
|---|---|
| One preferences page with several sections (custom UI); users toggle individual topics; emails use a single "Manage preferences" link | One subscription type, multiple subtypes (one per toggle). Your app groups subtypes into sections in the UI — ArtaMail does not need a type per section. |
| Different email streams need different unsubscribe links; Gmail one-click should only affect one category (e.g. community mail, not account alerts) | One type per category, with subtypes underneath. Use {{unsubscribe_<type_slug>}} in each template. |
| Simple app — one newsletter, one marketing list | One type, one marketing subtype. Lists handle segmentation. |
{{unsubscribe_community}} or Gmail one-click on that URL opts the contact out of all marketing and operational subtypes under the type community — not a single subtype. If sections must unsubscribe independently in the email footer, give each section its own type.Example: three preference sections
Suppose your app shows three blocks — Account activity, System & reports, and Community & roadmap — each with one or more toggles.
Option A — one type (common with custom UI)
Type: email-preferences├── account-activity (operational) "Real-time work updates"├── system-reports (operational) "Critical platform updates"└── community-roadmap (marketing) "Optional events & insights"→ One GET /api/u/{token}/data returns all subtypes→ Your UI renders three sections; POST preferences uses subtype slugs→ Emails use {{unsubscribe_url}} (global link)Option B — three types (section-specific unsubscribe in emails)
Type: account-activity└── work-progress (operational)Type: system-reports└── platform-alerts (operational)Type: community-roadmap└── events-insights (marketing) ← bind campaigns here→ Community campaigns use {{unsubscribe_community_roadmap}}→ One-click unsub on that link does not touch account-activity subtypesConfigure types in Dashboard → Subscription Types. See Custom Preference URLs for building your own preference page.
Subtype details
| Field | Description |
|---|---|
Subtype preference | Per-subtype opt-in row with provenance (consented_at, confirmed_at). No row = not subscribed (opt-in default). |
Channel | marketing — campaigns; operational — product mail with consent; transactional — receipts/auth (not toggled in preferences) |
Opt-in via API
Grant subtype consent when creating or updating a contact:
const res = await fetch('https://artamail.artatol.net/api/v1/contacts', { method: 'POST', headers: { Authorization: 'Bearer am_live_sk_xxx', 'Content-Type': 'application/json', }, body: JSON.stringify({ email: '[email protected]', name: 'Jane', locale: 'en', opt_in_subtype_slugs: ['weekly-digest'], }),});const contact = await res.json();// contact.pending_confirmation — true when org double opt-in applies// contact.confirm_url — send this in your consent-confirm emailtype_slug/subtype_slug in preference APIs.Double opt-in (DOI)
Enable Require double opt-in in Dashboard → Settings → Subscription consent. When on, API opt-ins set subscribed=true but leave confirmed_at null until the contact clicks confirm_url.
Your responsibilities
- Create a transactional template with slug
consent-confirm(preset available in dashboard). - Include
{{confirm_url}},{{name}},{{brand}},{{legal_entity}}, and optionally{{legal_address}}/{{legal_details}}in the template (use theconsent-confirmpreset as a starting point). - After
POST /api/v1/contacts, send the confirm email yourself — ArtaMail does not auto-queue it. - Contact clicks the link →
GET /api/confirm/[token]setsconfirmed_at. Optionally redirects using the success URL on the confirm template.
Configure the redirect URL on each consent template in the dashboard. Pass confirm_template on signup to bind token and email to the right app. See the Double Opt-In Redirect guide.
import { ArtaMail } from '@artatol/artamail-sdk';const artamail = new ArtaMail({ apiKey: process.env.ARTAMAIL_API_KEY! });// Option A: two stepsconst contact = await artamail.upsertContact({ email: '[email protected]', optInSubtypeSlugs: ['weekly-digest'],});if (contact.confirmUrl) { await artamail.sendConsentConfirmEmail({ to: contact.email, confirmUrl: contact.confirmUrl, name: contact.name, // brand, legal_entity auto-injected from account Settings });}// Option B: one callawait artamail.optInContact({ email: '[email protected]', optInSubtypeSlugs: ['weekly-digest'], sendConfirmEmail: true,});// Skip DOI (import, admin override)await artamail.upsertContact({ email: '[email protected]', optInSubtypeSlugs: ['weekly-digest'], doubleOptIn: false,});In-app settings (API key)
When users manage subscriptions inside your authenticated app (not via an email link), use the API key endpoints — no HMAC token required. Your server must verify the email belongs to the logged-in user before calling ArtaMail.
import { getArtaMail } from '@artatol/artamail-nextjs/server';const artamail = getArtaMail();// Load togglesconst prefs = await artamail.getContactSubscriptionPreferences(user.email);const types = await artamail.listSubscriptionTypes();// Save toggles (immediate confirm — no double opt-in on this endpoint)await artamail.updateContactSubscriptionPreferences(user.email, { preferences: { 'newsletter/weekly-digest': true },});// Master marketing switchawait artamail.updateContact(user.email, { subscribed: false });// Account deletion — permanent; PII and subscriptions removedawait artamail.deleteContact(user.email);PATCH /api/v1/contacts/.../subscription-preferences). Signed token — email preference center (POST /api/u/{token}/preferences).Campaigns
Marketing campaigns must bind at least one marketing subtype when your org has marketing subtypes configured. Sends filter recipients with is_contact_opted_in (subscribed + confirmed).
Multiple products
The default setup is one ArtaMail account per brand — isolated contacts, domains, API keys, and a preferences_url_templateper account. Set brand, legal entity, legal address, and legal details in Settings → Brand & compliance.
Multilingual preference pages
User-facing preference and unsubscribe pages resolve text from contacts.locale, with fallback to your account's supported locales (Settings → Contact locales) and then English.
What lives where
| Layer | Fields | Used when | |
|---|---|---|---|
| Subtype | label_i18n | , description_i18n | Toggle row label and description on every preference page |
| Subscription type | custom_html | , show_unsubscribe_all | Optional custom HTML template and global-unsubscribe toggle. Wrap header, intro, and footer in artamail-form-only so they hide on save. Placeholders ({{title}}, {{success_button}}, etc.) come from account settings. |
| Account settings | preference_page.global_copy | , one_click_copy | All preference pages (global and typed tokens): title, subtitle, footer, buttons, success/error messages, optional success CTA (success_button_text / success_button_href). Default hosted UI: /preferences/{token} (legacy /unsubscribe/* redirects) |
| Contact | locale | Picks which translation to render; set via API, import, or dashboard |
Configure per-locale copy in Settings → Preference page. Subtype labels use locale tabs on Subscription Types. The API exposes subtype i18n via Subscription Types API; Preferences Data API returns resolved strings for custom UIs.
label in the dashboard is an admin identifier. The heading users see is copy.title from account global_copy, or use {{type_title}} in that copy for typed links.Custom preference URLs
Optionally set a preferences_url_template in dashboard settings (e.g. https://prefs.example.com/manage?token={{token}}). When set, ArtaMail substitutes the signed token at send time for {{unsubscribe_url}} and List-Unsubscribe headers for all marketing sends in the org. Use {{unsubscribe_token}} when you build per-brand URLs yourself in template HTML instead.