Guide

Subscriptions & Consent

How subscription types, subtype consent, campaigns, and double opt-in work in ArtaMail.

Quick rule
Multiple brands → separate accounts. Multiple topics in one app → usually one type + many subtypes (custom preference UI). Use several types only when each email category needs its own {{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:

LevelWhat it isExample
AccountIsolated workspace — one brand/product, own contacts and API keySuperWidget app
Subscription typeConsent category inside an account. Drives typed unsubscribe links {{unsubscribe_<slug>}} and optional per-category preference pages.Community & roadmap
Subscription subtypeAtomic opt-in — what campaigns, API opt_in_subtype_slugs, and preference saves actually use. Channel: marketing, operational, or transactional.platform-events
Contact listsSegmentation only — joining a list does not grant consentVIP customers list
contacts.subscribedMaster 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 situationRecommended model
One preferences page with several sections (custom UI); users toggle individual topics; emails use a single "Manage preferences" linkOne 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 listOne type, one marketing subtype. Lists handle segmentation.
Typed unsubscribe is per type, not per subtype
A link like {{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)

text
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)

text
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 subtypes

Configure types in Dashboard → Subscription Types. See Custom Preference URLs for building your own preference page.

Subtype details

FieldDescription
Subtype preferencePer-subtype opt-in row with provenance (consented_at, confirmed_at). No row = not subscribed (opt-in default).
Channelmarketing — 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:

signup.ts
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({
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 email
Slug scope
Subtype slugs are unique per subscription type, not globally per account. Use unambiguous slugs or the composite form type_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

  1. Create a transactional template with slug consent-confirm (preset available in dashboard).
  2. Include {{confirm_url}}, {{name}}, {{brand}}, {{legal_entity}}, and optionally {{legal_address}} / {{legal_details}} in the template (use the consent-confirm preset as a starting point).
  3. After POST /api/v1/contacts, send the confirm email yourself — ArtaMail does not auto-queue it.
  4. Contact clicks the link → GET /api/confirm/[token] sets confirmed_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.

doi-flow.ts
import { ArtaMail } from '@artatol/artamail-sdk';
const artamail = new ArtaMail({ apiKey: process.env.ARTAMAIL_API_KEY! });
// Option A: two steps
const contact = await artamail.upsertContact({
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 call
await artamail.optInContact({
optInSubtypeSlugs: ['weekly-digest'],
sendConfirmEmail: true,
});
// Skip DOI (import, admin override)
await artamail.upsertContact({
optInSubtypeSlugs: ['weekly-digest'],
doubleOptIn: false,
});
Preference center
When a contact opts in via the preference center (signed unsubscribe link), consent is confirmed immediately — no follow-up email. This is intentional for in-app / link-based flows.

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.

settings.ts
import { getArtaMail } from '@artatol/artamail-nextjs/server';
const artamail = getArtaMail();
// Load toggles
const 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 switch
await artamail.updateContact(user.email, { subscribed: false });
// Account deletion — permanent; PII and subscriptions removed
await artamail.deleteContact(user.email);
Two auth models
API key — in-app settings (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.

Multiple Products guide →

Advanced: shared contacts across apps in one account

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

, , ,
LayerFieldsUsed when
Subtypelabel_i18ndescription_i18nToggle row label and description on every preference page
Subscription typecustom_htmlshow_unsubscribe_allOptional 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 settingspreference_page.global_copyone_click_copyAll 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)
ContactlocalePicks 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.

Admin label vs page title
The type 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.

Custom Preference URLs guide →