> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stackshift.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriber consent and imports

> Preview imports row by row, preserve consent evidence and use double opt-in before marketing delivery.

<Warning>
  **Live with caveats.** This area is real and usable, but the docs intentionally call out operational or UX limits that still matter.
</Warning>

## Goal

Preview imports row by row, preserve consent evidence and use double opt-in before marketing delivery.

## Prerequisites

* Keep Mail credentials on your backend. API examples use [https://api.stackshift.cloud/v1](https://api.stackshift.cloud/v1) unless a public /api/v1 route is stated.

## Workflow

<Steps>
  <Step>
    Create a brand and audience and preserve source consent evidence.
  </Step>

  <Step>
    Preview recorded\_consent or confirmation rows and review every outcome.
  </Step>

  <Step>
    Commit, download the dashboard error report, and retry corrected rows.
  </Step>

  <Step>
    Wait for explicit confirmation before marketing to pending members.
  </Step>
</Steps>

## Import routes and modes

* POST /mail/audiences/\{id}/imports/preview and POST /mail/audiences/\{id}/imports accept the same JSON: mode and members. Preview validates against current membership/suppression state without writing contacts or sending confirmations. Commit rechecks current state; preview is not a reservation.
* mode is recorded\_consent (default) or confirmation. Both require consentSource and consentAt for every valid row. recorded\_consent imports eligible new members as subscribed. confirmation creates eligible new members as pending\_confirmation and queues confirmation delivery.
* Use 1–10,000 rows per request; the HTTP body limit is 16 MiB. Invalid member rows do not reject otherwise valid rows. Malformed top-level JSON, unsupported mode, empty/oversized imports or unavailable confirmation delivery reject the request.
* An existing subscribed contact can receive a shared profile update; import does not replace its existing consent record or force it back into pending. Existing unsubscribed, suppressed or pending memberships are skipped, not reactivated.

## Required evidence and validation

* email must be a bare address without display name, at most 254 bytes; trim and lowercase are applied. Duplicate normalized addresses after the first valid row are skipped.
* consentSource is required and at most 120 bytes after trimming. consentAt is a valid RFC3339 timestamp and cannot be more than five minutes in the future. consentIp, when supplied, must be an IPv4/IPv6 address.
* name is at most 200 bytes after trimming. attributes is a map of at most 50 string fields. Keys are normalized to lowercase and match ^\[a-z]\[a-z0-9\_]\{0,63}\$; normalized duplicates are invalid. Values are trimmed and limited to 500 bytes. Null characters are rejected.
* Preserve real opt-in evidence from the original source; importing a list is not permission to contact it.

## Dashboard CSV format

* Upload a CSV file of at most 16 MiB or paste CSV text. Required headers are email, consent\_source and consent\_at; optional reserved headers are name and consent\_ip. Other valid column names become custom attributes. Headers are trimmed and lowercased, and must be unique.
* Use RFC3339 consent timestamps. Quoted commas and escaped quotes are supported. Missing headers, inconsistent column counts or unclosed quotes are structural errors: fix them before preview. Invalid email or consent values are handled in the row-level results.
* The Upload → Preview → Results flow lets you filter outcomes and inspect 50 rows per page. Download error CSV is available for invalid/skipped rows in preview or results. The file subscriber-import-errors.csv retains original fields and CSV line numbers, adds import\_row/import\_outcome/import\_code/import\_reason, and escapes formula-like values for spreadsheet safety.
* The browser does not retain an import-history archive. Keep your report if needed; remove the added import\_\* report columns before reimporting so they are not treated as subscriber attributes.

## Preview, inspect and commit

Use client authenticated in the audience’s environment and the audience returned by brand-backed creation. The sample timestamp represents recorded evidence; replace it with the actual consent time.

```ts theme={null}
const members = [{
  email: 'ada@example.net', name: 'Ada', consentSource: 'website-signup',
  consentAt: '2026-08-31T10:00:00Z', attributes: { company: 'Example' },
}, { email: 'not-an-address', consentSource: 'website-signup', consentAt: '2026-08-31T10:00:00Z' }]
const preview = await client.mail.audiences.previewImport(audience.id, members, 'confirmation')
console.table(preview.rows)
// Review the preview before committing; valid rows can succeed despite invalid rows.
const result = await client.mail.audiences.importDetailed(audience.id, members, 'confirmation')
console.log(result.summary)
```

## Result contract and recovery

* The response contains preview, rows and summary. summary has imported, updated, invalid, skipped and pending. Each row has a 1-based row number, email, outcome, code, reason and optional status. pending counts accepted imported/updated rows whose status is pending\_confirmation.
* Legacy top-level imported equals summary.imported + summary.updated; top-level skipped equals summary.skipped + summary.invalid. Use importDetailed rather than the legacy JavaScript import wrapper when you need the full result type.
* Validation codes include invalid\_fields, invalid\_email, consent\_evidence\_required, invalid\_name, invalid\_consent\_ip, invalid\_attributes and duplicate\_attribute. duplicate\_email identifies repeated addresses; consent\_protected preserves opt-outs/suppressions/pending state; confirmation\_rate\_limited means retry later; profile\_updated means an existing subscribed profile changed.
* The dashboard downloads a CSV error report from returned rows. There is no separate server-side import-report download endpoint. Save rows in your integration if you need a report later. Correct invalid rows and retry only the intended rows; do not remove consent protections to make an import pass.

## Double opt-in and public pages

Subscriber pages are public routes on the API origin under /mail, outside the authenticated /v1 prefix. Follow the full link in the confirmation message or dashboard form instead of constructing a token URL.

* Signup forms use double opt-in. Only confirmed subscribed members are eligible for marketing and subscription-triggered automation. A public response does not disclose whether an email already exists, is suppressed or is rate limited.
* Confirmation links expire after 24 hours and require POST /mail/confirm?token=... to subscribe. GET only renders the review page, so a link scanner does not activate a subscription. Expired, consumed or unavailable challenges return HTTP 410.
* Confirmation requests are limited to three per recipient/workspace/environment per day and a 15-minute resend cooldown, as well as available confirmation/sending budget. Limits may skip a row rather than create an undeliverable pending member.
* GET/POST /mail/preferences?token=... shows or changes existing subscriptions. save changes name/approved attributes; unsubscribe affects a membership; subscribe requests fresh confirmation; unsubscribe\_all uses the token’s brand/environment (or original workspace scope for legacy links). Rejoining is never an import bypass.
* In Test, confirmation is simulated and subscriber pages state that no email was delivered. Open the confirmation link from the test message log.
* Broadcast Manage preferences links expire after 90 days. Confirmation mail has no open/click tracking. Public signup does not overwrite an already active subscriber’s shared profile.

## Forms, segments and automations

* GET/POST /mail/forms requires audienceId, name, heading, buttonLabel and successMessage; description is optional. Nonempty limits are name 120 bytes, heading 160, buttonLabel 60 and successMessage 240; description is at most 500 bytes. The server requires these fields even where older SDK types mark them optional. The form response contains publicToken and inherited brandId/mode; use the dashboard’s form link for the hosted signup page.
* GET/POST /mail/segments accepts audienceId, name, optional description, field, operator and value. Operators are equals, not\_equals, contains and exists; value is required except for exists and is at most 500 bytes. GET/POST /mail/automations accepts audienceId, name, template, optional versionId, from, optional delaySeconds and simulation. Automation delaySeconds is 0–2,592,000 (30 days); the supported trigger is contact\_added. These are create/list surfaces; no update/delete endpoints are exposed here.
* PUT /mail/audiences/\{id}/preferences with editableFields remains a compatibility endpoint. It changes the audience’s brand-wide approved fields, not an independent per-list policy. Prefer editing the brand.

## Expected result

<Check>
  Import results identify accepted and rejected rows; only confirmed, subscribed and unsuppressed memberships become eligible for marketing sends.
</Check>

## Related guides

<CardGroup cols={2}>
  <Card title="StackShift Mail overview" href="/stackshift-mail/overview">
    Mail APIs and workflows: isolated simulation, explicit brands, consent-aware campaigns, engagement, domain health, migration, spending, inbound delivery and diagnostics.
  </Card>

  <Card title="Events, webhooks, and timelines" href="/stackshift-mail/events-webhooks-and-timelines">
    List mail events, inspect per-message timelines, subscribe webhooks, rotate secrets, retry deliveries, and verify webhook signatures.
  </Card>

  <Card title="Brands and shared contacts" href="/stackshift-mail/brands-and-contacts">
    Create explicit sending identities and share contact profiles within a brand while keeping audience consent independent.
  </Card>

  <Card title="Streams, audiences, and campaigns" href="/stackshift-mail/streams-audiences-and-campaigns">
    Separate transactional and broadcast traffic, retain consent evidence, and build private campaign drafts with unsubscribe protection.
  </Card>

  <Card title="Test environment" href="/stackshift-mail/test-environment">
    Exercise delivery, bounce, complaint and delay paths with isolated Mail data and no real email delivery.
  </Card>
</CardGroup>
