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 unless a public /api/v1 route is stated.
Workflow
1
Create a brand and audience and preserve source consent evidence.
2
Preview recorded_consent or confirmation rows and review every outcome.
3
Commit, download the dashboard error report, and retry corrected rows.
4
Wait for explicit confirmation before marketing to pending members.
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.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
Import results identify accepted and rejected rows; only confirmed, subscribed and unsuppressed memberships become eligible for marketing sends.
Related guides
StackShift Mail overview
Mail APIs and workflows: isolated simulation, explicit brands, consent-aware campaigns, engagement, domain health, migration, spending, inbound delivery and diagnostics.
Events, webhooks, and timelines
List mail events, inspect per-message timelines, subscribe webhooks, rotate secrets, retry deliveries, and verify webhook signatures.
Brands and shared contacts
Create explicit sending identities and share contact profiles within a brand while keeping audience consent independent.
Streams, audiences, and campaigns
Separate transactional and broadcast traffic, retain consent evidence, and build private campaign drafts with unsubscribe protection.
Test environment
Exercise delivery, bounce, complaint and delay paths with isolated Mail data and no real email delivery.