Email Lists
Create subscriber lists for newsletters, announcements, and marketing broadcasts.
Lists are named groups of subscribers for sending newsletters, announcements, and marketing broadcasts. Unlike segments (dynamic, filter-based), lists use explicit subscribe/unsubscribe membership.
Create a list
POST https://api.molted.email/v1/agent/listsmolted lists create --name "Weekly Digest" --type newsletter --double-opt-incurl -X POST https://api.molted.email/v1/agent/lists \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Digest",
"type": "newsletter",
"doubleOptIn": true,
"description": "Weekly product updates"
}'List types
| Type | Use case |
|---|---|
newsletter | Recurring content (weekly digest, blog roundup) |
announcement | Product updates, release notes |
marketing | Promotional campaigns, offers |
List all lists
GET https://api.molted.email/v1/agent/listsmolted lists list
molted lists list --limit 10 --offset 20Get a list
GET https://api.molted.email/v1/agent/lists/:idmolted lists get <list-id>Update a list
PATCH https://api.molted.email/v1/agent/lists/:idmolted lists update <list-id> --name "New Name" --description "Updated description"
molted lists update <list-id> --status pausedUpdatable fields: name, description, type, status, doubleOptIn.
Archive a list
DELETE https://api.molted.email/v1/agent/lists/:idmolted lists archive <list-id>Archiving soft-deletes the list. Subscribers are preserved but no new sends can be initiated.
Subscribe a contact
POST https://api.molted.email/v1/agent/lists/:id/subscribersmolted lists subscribe <list-id> --email alice@example.com --name "Alice"If double opt-in is enabled on the list, the subscriber status will be pending until they confirm via the confirmation email.
Double opt-in
When doubleOptIn: true is set on a list, subscribing a contact triggers a confirmation flow:
- The contact is added with
status: pending. - A
list.subscribe.pendingjourney event is emitted automatically. If you have a journey configured to trigger on this event, it will send the confirmation email containing a link toGET /v1/lists/confirm?token=<token>. - When the contact clicks the link, their status is updated to
activeand the listsubscriberCountis incremented.
Confirmation tokens expire after 48 hours. Pending subscriptions that are never confirmed are automatically deleted after 7 days.
Setting up the confirmation journey
Create a journey in the portal with trigger event list.subscribe.pending. Use the confirmationUrl field from the event payload in your confirmation email template:
{
"listId": "...",
"listName": "Weekly Digest",
"contactId": "...",
"confirmationUrl": "https://api.molted.email/v1/lists/confirm?token=..."
}The journey step that sends the confirmation email should include {{ payload.confirmationUrl }} as the confirm link.
Subscription statuses
| Status | Meaning |
|---|---|
pending | Awaiting confirmation (double opt-in only) |
active | Confirmed subscriber |
unsubscribed | Opted out |
Unsubscribe a contact
POST https://api.molted.email/v1/agent/lists/:id/unsubscribemolted lists unsubscribe <list-id> --email alice@example.comList subscribers
GET https://api.molted.email/v1/agent/lists/:id/subscribersmolted lists subscribers <list-id>
molted lists subscribers <list-id> --limit 100 --offset 0members is an alias for subscribers - both commands do exactly the same thing:
molted lists members <list-id> # equivalent to 'subscribers'Note: The CLI help screen shows
subscribers|membersusing pipe notation to indicate the alias. This is not a special syntax you need to type - use eithersubscribersormembersas the command name.
Bulk import subscribers
molted lists import <list-id> --file subscribers.csv
molted lists import <list-id> --file subscribers.jsonThe file format is determined by extension: .csv for CSV, anything else is parsed as JSON.
CSV format
The first row must be a header row. The email column is required. name is optional. Any additional columns are collected into a metadata object on the subscriber.
email,name,plan,source
alice@example.com,Alice,pro,website
bob@example.com,Bob,starter,referral
charlie@example.com,,free,importJSON format
Must be a top-level JSON array. Each element is a subscriber object. Fields beyond email and name should go inside a metadata object.
[
{ "email": "alice@example.com", "name": "Alice", "metadata": { "plan": "pro" } },
{ "email": "bob@example.com", "name": "Bob", "metadata": { "plan": "starter" } }
]Subscriber fields
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | Yes | Rows/objects with a missing or empty email are silently skipped. |
name | string | No | Display name. |
| Additional CSV columns | string | No | Collected into a metadata object automatically. |
metadata (JSON) | object | No | Arbitrary key-value pairs. Passed through as-is. |
Validation errors
| Error | Cause |
|---|---|
Cannot read file: <path> | File does not exist or cannot be read. |
File must contain a valid JSON array of subscriber objects | JSON is malformed or is not an array. |
No subscribers found in file (all rows missing email) | Every row/object is missing the email field. |
Import response
Files are processed in chunks of 500. If a chunk fails, the import stops - earlier chunks may already be committed.
{
"status": "success",
"message": "Bulk subscribe completed: 150 subscribed, 3 already subscribed, 0 failed.",
"summary": {
"total": 153,
"subscribed": 150,
"alreadySubscribed": 3,
"failed": 0
},
"results": [
{ "email": "alice@example.com", "outcome": "subscribed" },
{ "email": "bob@example.com", "outcome": "already_subscribed" }
]
}Send to a list
POST https://api.molted.email/v1/agent/lists/:id/sendmolted lists send <list-id> --template <template-id> --yes
molted lists send <list-id> --template <template-id> --mailbox <mailbox-id> --payload '{"promo_code": "SPRING25"}' --yesAlways pass --yes when sending from an agent or script. Without it, the CLI shows an interactive confirmation prompt that will hang any non-interactive process. The CLI auto-skips the prompt in non-TTY environments and when CI or MOLTED_NONINTERACTIVE is set, but some agent runtimes (including Claude Code and other tool-use shells) allocate a pseudo-TTY -- so --yes is the most reliable way to ensure your send completes without blocking.
Broadcasts are sent sequentially to each active subscriber. For each recipient, the system:
- Renders the template with
subscriberEmail,subscriberName,listName, andunsubscribeUrlinjected into the payload - Generates a unique dedupe key (
list-{listId}-{sendId}-{contactEmail}) to prevent duplicate sends - Attaches RFC 8058
List-UnsubscribeandList-Unsubscribe-Postheaders for one-click unsubscribe - Runs every send through the full policy engine (rate limits, suppressions, cooldowns)
The response includes sent, blocked, failed, and blockedReasons counts:
sent- send requests accepted by the policy engine (queued, scheduled, or pending approval)blocked- sends the policy engine refused for this recipient (e.g.suppressed,trial_not_activated,rate_limited)failed- sends that threw an upstream error (non-policy)blockedReasons- map of block reason to count, e.g.{ "suppressed": 2 }
Partial failures do not abort the broadcast - the system continues sending to remaining subscribers. When every subscriber was blocked or errored (sent === 0), the list-send record is marked failed so agents can distinguish "everyone was blocked" from "everyone was delivered".
Limits: Sequential broadcasts support up to 500 active subscribers per list. Lists exceeding this limit will return an error. For larger lists, contact support.
View send history
GET https://api.molted.email/v1/agent/lists/:id/sendsmolted lists sends <list-id>CLI output format
All list commands use a structured JSON envelope for agent-friendly parsing:
{
"status": "success",
"message": "List 'Weekly Digest' created successfully.",
"data": { "id": "...", "name": "Weekly Digest", "subscriberCount": 0 }
}{
"status": "error",
"message": "Failed to create list",
"details": { "error": "..." }
}For bulk import response structure, see Bulk import subscribers.