Contacts, audiences and broadcasts
Every command for the address book, audiences, broadcasts and the suppression list, with worked examples.
How they fit together
Four namespaces cover the people you write to. Contacts are the workspace address book, audiences are named lists of contacts, a broadcast sends one message to everybody in some audiences, and the suppression list holds the addresses the workspace will not send to. Each command calls one SDK method, so the SDK pages describe the same calls in more depth.
- A contact has no id. Its address is the key every
contactscommand takes, trimmed and lower cased, soGrace@Example.comandgrace@example.comare one contact. An audience has anaud_id, a broadcast abrd_id, and a suppression the idsuppressions listprints. - Every contact is in the default audience for as long as it exists. That audience cannot be deleted, emptied or thinned, and
builtinisdefaulton it. - The address book belongs to the workspace, so every member and every key reads and writes the same one.
- Each namespace also answers to its singular, as in
nextmail contact get, and the usual aliases work:ls,show,new,editandrm. Insuppressions, whose verbs areaddandremove,newleads toaddandrmtoremove.
nextmail <namespace> <verb> --help shows every flag with its type, the scopes, the endpoint and what the command returns. Add --json for the same page as data.
Contacts
The workspace address book: the people a member has written to from the app composer, plus anybody saved by hand. Mail that arrives adds nobody, and neither does a send through the API or the CLI.
| Command | What it does |
|---|---|
| nextmail contacts list | One page of the saved contacts, most recently mailed first. --source keeps manual or auto contacts, and --q searches names and addresses |
| nextmail contacts get <email> | One contact, with every audience it is in |
| nextmail contacts create --email <value> | Save a new contact, with --name, --notes, --audience-ids and custom fields in --attributes. An address already in the book is refused with 409 contact_exists |
| nextmail contacts update <email> | Change --name or --notes, where null clears one, or custom fields with --attributes, a JSON object where null removes a field. The address itself cannot change |
| nextmail contacts delete <email> | Delete the contact with its notes, photo and memberships, and hide the address so the composer does not record it again |
| nextmail contacts set-audiences <email> --audience-ids <a,b> | Make the audiences the contact is in exactly this list. The default audience is always kept |
| nextmail contacts list-people | Everyone on the Contacts page: the saved contacts and, with threads:read, every address seen in mail, with thread counts. --sort, --q, --email and --blocked narrow it |
| nextmail contacts save <email> | Save an address, keep one recorded from a send, or bring back a deleted one. Never an error, whatever state the address is in |
| nextmail contacts delete-many <emails...> | Delete and hide 1 to 200 addresses in one call |
| nextmail contacts set-photo <email> <data> | Upload the photo from a file, or from stdin with -: PNG, JPEG, WebP or GIF up to 5 MB |
| nextmail contacts remove-photo <email> | Take the photo off and delete the stored image |
| nextmail contacts block <email> | Put the address on the workspace blocklist, so mail from it is refused. A plus tag is dropped |
| nextmail contacts unblock <email> | Take off every blocklist rule that blocks the address, a whole-domain rule included |
| nextmail contacts list-threads <email> | The threads the address wrote or was written to, in every folder. --q searches inside them |
| nextmail contacts activity <email> | Mail received from and sent to the address over a window, 90 days unless --minutes says otherwise, with the threads waiting on a reply and the median reply time each way |
| nextmail contacts import-vcf <data> | Import a .vcf file, read from a path or from stdin with -. Every card becomes a contact card, and imported contacts join no audience |
| nextmail contacts list-imports | List one page of the contacts imports, newest first, with what each added and left out |
| nextmail contacts get-import <id> | Read one contacts import with its report |
| nextmail contacts undo-import <id> | Remove every card one import added. Asks you to confirm |
Audiences
Named lists of contacts, up to 100 in a workspace. An address has to be a contact before it joins one, except through import-contacts, which saves new addresses as it goes.
| Command | What it does |
|---|---|
| nextmail audiences list | One page of the audiences, the default one first and the rest newest first, each with its contactCount |
| nextmail audiences growth | How the audiences grew over a window, 30 days unless --days or --minutes says otherwise: joins and unsubscribes per bucket, and totals |
| nextmail audiences get <id> | One audience, with a fresh contactCount |
| nextmail audiences create --name <value> | Create an empty audience, with an optional --description. Names are not unique |
| nextmail audiences update <id> | Change --name or --description. Membership is not touched |
| nextmail audiences delete <id> | Delete the audience and keep its contacts. The default audience cannot be deleted |
| nextmail audiences empty <id> | Take every contact out and keep the audience, with its id, name and description |
| nextmail audiences list-contacts <id> | One page of the contacts in the audience, with when each joined and whether it unsubscribed. --sort, --q, --source and --statuses narrow it |
| nextmail audiences add-contact <id> --email <value> | Put one existing contact in the audience. Adding somebody who is already there changes nothing |
| nextmail audiences remove-contact <id> <email> | Take one contact out. A contact that is not in the audience is a 404 |
| nextmail audiences add-contacts <id> --emails <a,b> | Put up to 200 existing contacts in, and report the addresses that are not contacts in missing |
| nextmail audiences remove-contacts <id> --emails <a,b> | Take up to 200 contacts out, and report the ones that were not in it |
| nextmail audiences import-contacts <id> --contacts <json|@file|-> | Import up to 500 { email, name } rows, saving the addresses that are not contacts yet |
Broadcasts
One message to everybody in up to 10 audiences, sent as a separate copy for each person, with the merge fields filled in and an unsubscribe link. Each copy is an ordinary email with its own msg_ id, events and webhooks.
| Command | What it does |
|---|---|
| nextmail broadcasts preview --audience-ids <a,b> | Count who a broadcast to these audiences would reach, and who it would skip as unsubscribed or suppressed. Sends nothing |
| nextmail broadcasts send --audience-ids <a,b> --from <value> | Send with --subject and --html or --text, or a stored --template, now or at --scheduled-at |
| nextmail broadcasts list | One page of broadcasts, newest first, with live counts. --audience-id keeps the ones sent to that audience |
| nextmail broadcasts get <id> | One broadcast, with its status and live counts: the command to poll while it sends |
| nextmail broadcasts stats <id> | Totals of delivered, bounced, opened, clicked and unsubscribed, and a series per --grain bucket, an hour unless you say. --days or --minutes adds a window of what happened lately |
| nextmail broadcasts analytics | How the broadcasts sent over a window did, 30 days unless --days or --minutes says otherwise: totals, a series per --grain bucket and a row per broadcast. --broadcast-ids picks the ones added up |
| nextmail broadcasts list-recipients <id> | Who each copy went to and what happened to it. --filter keeps one group, such as bounced or not_opened |
| nextmail broadcasts get-recipient <id> <email-id> | One person's copy, with the subject, HTML and text exactly as they received them |
| nextmail broadcasts cancel <id> | Stop a broadcast that is scheduled, queued or still sending. Copies that have gone cannot be recalled |
Suppressions
The addresses this workspace will not send to: hard bounces and complaints, recorded as they happen, and any address you add by hand. A send to one is refused for that recipient before anything leaves.
| Command | What it does |
|---|---|
| nextmail suppressions list | One page of the list, newest first. --reason keeps bounce, complaint or manual, and --q searches |
| nextmail suppressions get <id> | One row: the address, the reason, the detail the bounce or complaint carried, and whether it can be removed |
| nextmail suppressions add --email <value> | Stop sending to an address. Adding one that is there already returns the row it holds |
| nextmail suppressions remove <id> | Allow mail to the address again. A hard bounce cannot be removed |
Suppressions and the blocklist are different lists. suppressions add stops mail going out to an address, and contacts block refuses mail coming in from it.
Scopes
Most commands need the read or write scope of their namespace. A few need another, because they read or change something else:
| Scope | Commands |
|---|---|
| contacts:read | contacts list, get and list-people |
| contacts:write | contacts create, update, delete, save, delete-many, set-photo and remove-photo, and audiences import-contacts beside audiences:write |
| audiences:read | audiences list, growth, get and list-contacts, and broadcasts preview, so a key that cannot send can still show the count |
| audiences:write | Every other audiences command, and contacts set-audiences. contacts create --audience-ids needs it beside contacts:write |
| threads:read | contacts list-threads and activity, and the addresses seen in mail in list-people |
| settings:read | suppressions list and get |
| settings:write | suppressions add and remove, and contacts block and unblock |
| emails:read | broadcasts list, get, stats, analytics, list-recipients and get-recipient |
| emails:send | broadcasts send, which needs audiences:read too, and broadcasts cancel |
- A key limited to particular addresses or domains reads and writes the same address book as every other key. It sees only the broadcasts sent from an address or domain it holds, gets the saved contacts alone from
list-people, and is refused with 422capability_unsupportedbycontacts list-threads,activity,blockandunblock, and bysuppressions addandremove. - A browser sign-in by a member who reaches only some addresses is refused with 422
capability_unsupportedon everycontacts,audiencesandbroadcastscommand.suppressions addrefuses a browser sign-in by anybody but the workspace owner.
Worked examples
Build an audience from a file, then count who a broadcast to it would reach. import-contacts saves the addresses that are not contacts yet, and running it again creates and adds nothing twice.
[ { "email": "ada@example.com", "name": "Ada Lovelace" }, { "email": "grace@example.com", "name": "Grace Hopper" }, { "email": "katherine@example.com" }]AUDIENCE=$(nextmail audiences create --name 'Product updates' --json | jq -r .id)nextmail audiences import-contacts "$AUDIENCE" --contacts @contacts.jsonnextmail broadcasts preview --audience-ids "$AUDIENCE"Check a broadcast with --dry-run, which prints the request and sends nothing, then send it. The broadcast is created at once and sent in the background, so poll get to follow it. This body does not place {{unsubscribeUrl}}, so every copy gets a one-line unsubscribe footer.
{ "audienceIds": ["aud_4c1b8e2a7d9f05c36b4e8a71"], "from": "Acme <news@acme.com>", "subject": "{{firstName|Hello}}, the September release is out", "html": "<p>Hi {{firstName|there}},</p><p>Here is what changed this month.</p>", "scheduledAt": "2026-10-01T09:00:00Z"}nextmail broadcasts send --data @broadcast.json --dry-runBROADCAST=$(nextmail broadcasts send --data @broadcast.json --yes --json | jq -r .id)nextmail broadcasts get "$BROADCAST"nextmail broadcasts stats "$BROADCAST" --grain daySee who a broadcast did not reach. --ndjson prints one recipient per line, and --all --json one document with every page.
nextmail broadcasts list-recipients brd_5a8c1e3f7b2d94a06c8e1f3b --filter bounced --ndjson | jq -r .emailnextmail broadcasts list-recipients brd_5a8c1e3f7b2d94a06c8e1f3b --filter not_opened --all --json | jq ".items | length"nextmail suppressions list --reason bounce --all --max 50Copy the subscribed members of one audience into another. jq turns the stream into the body add-contacts takes, and --data - reads it from stdin. --max 200 keeps it to the 200 addresses one call accepts.
nextmail audiences list-contacts aud_9f2c4b7e1a0d63d84c5f2e7b --statuses subscribed --max 200 --ndjson \ | jq -s '{ emails: map(.email) }' \ | nextmail audiences add-contacts aud_1c4e7a9b2d0f36e85a7c1b4d --data -Delete every contact the composer recorded at one domain. delete-many takes up to 200 addresses a call, so xargs -n 200 splits a longer list. Check the batches with --dry-run first, because there is no undo.
nextmail contacts list --source auto --all --ndjson \ | jq -r 'select(.email | endswith("@old-vendor.example")) | .email' > leaving.txtxargs -n 200 nextmail contacts delete-many --dry-run < leaving.txtxargs -n 200 nextmail contacts delete-many --yes < leaving.txtStop sending to an address, allow one again, and block a sender. removable says which rows suppressions remove will take.
nextmail suppressions add --email noreply@example.comnextmail suppressions list --q ada@example.com --json | jq -r '.items[] | select(.removable) | .id'nextmail suppressions remove 7b1e2c3d-4f5a-4b6c-8d7e-9f0a1b2c3d4e --yesnextmail contacts block spammer@example.comConfirmations and verification codes
These commands ask you to confirm in a terminal before they run:
| Namespace | Asks to confirm |
|---|---|
| contacts | delete, delete-many, remove-photo and unblock |
| audiences | delete, empty, remove-contact and remove-contacts |
| broadcasts | send and cancel |
| suppressions | remove |
--yesconfirms for you. Unattended, with--jsonor--no-input, in CI or without a terminal, a command that would ask stops withRefusing to run unattended. Pass --yes to confirm.and exit code2.--dry-runprints the request the command would send and exits with code0, without asking and without changing anything.- With a browser sign-in,
audiences deleteasks for a verification code first, as the web app does.--yesnever skips it, and unattended the command stops with exit code4. Runnextmail verifybeforehand, or use an API key, which is never asked. audiences emptynever asks for a verification code, so check the id before you pass--yes.
Paging
Every command that lists reads one page. When more remain, pass the cursor it printed to --cursor, with the same filters, or read them all:
--allreads every page and streams the items: a table on a terminal, and one JSON object per line when piped or with--ndjson.--max <n>stops after that many items, and implies--all.--jsonprints one{ items, hasMore, nextCursor }document,--allincluded.- A malformed or stale cursor is a 400
invalid_cursor. Start again without one.
| Command | Page size |
|---|---|
| nextmail contacts list | 1 to 200, 50 unless --limit says otherwise |
| nextmail contacts list-people | 1 to 100, 25 unless --limit says otherwise |
| nextmail contacts list-threads | 1 to 100, 25 unless --limit says otherwise |
| nextmail audiences list | 1 to 100, 25 unless --limit says otherwise |
| nextmail audiences list-contacts | 1 to 200, 50 unless --limit says otherwise |
| nextmail broadcasts list | 1 to 100, 25 unless --limit says otherwise |
| nextmail broadcasts list-recipients | 1 to 200, 50 unless --limit says otherwise |
| nextmail suppressions list | 1 to 100, 25 unless --limit says otherwise |
Good to know
contacts createrefuses an address already in the book with 409contact_exists, so a retry never overwrites a name somebody edited.contacts savenever refuses: it saves, keeps or brings back the address, whatever state it is in.contacts deletealso takes an address that was only ever seen in mail, which takes that person offlist-people. The mail stays. There is no undo: saving the address again starts a contact with no name, no notes and no audience beyond the default one.- The address is the identity of a contact, so
contacts updatecannot change it. Moving a contact is adeleteand acreate. contacts set-photoreads the image from a file, or from stdin with-. Pass--content-type, such asimage/jpeg: without it the image can go asapplication/octet-stream, which the server refuses with 422invalid_image.broadcasts send --scheduled-attakes an ISO 8601 time such as2026-10-01T09:00:00Z, or an ISO 8601 duration such asPT2HorP1D, up to 365 days out. The short delayssend --attakes, such as2h, are refused here.- Merge fields work in
--subject,--htmland--text:{{firstName}},{{lastName}},{{name}},{{email}}and{{unsubscribeUrl}}, each with a fallback after a bar, as in{{firstName|there}}. A body that does not place{{unsubscribeUrl}}gets a one-line unsubscribe footer. A template is sent as it is, so put the link in the template. - A custom field of the contact is
{{attributes.<key>}}in--subject,--htmland--text, such as{{attributes.plan|Free}}. - A broadcast is checked against the monthly sends of the plan before anything is written, and each copy counts as one send. One the allowance cannot cover is refused with 429
send_quota_exceeded, and nothing is left behind. - Pass your own
--idempotency-keytobroadcasts sendwhen a script may run the step again. The same key answers with the broadcast it created instead of sending a new one. - A contact who unsubscribes from a broadcast stays in the audience with
unsubscribedAtset, and later broadcasts to that audience skip it.audiences list-contacts --statuses unsubscribedlists them. - A hard bounce stays on the suppression list.
suppressions removerefuses it with 409suppression_not_removable, andremovableon each row says so in advance.
Google Workspace