Skip to the documentation
Python

nextmail.broadcasts

Every method in this namespace: its signature, its parameters, what it returns and an example.

Methods

One message sent to everybody in one or more audiences, a personalised copy for each person, with unsubscribe handled for you.

broadcasts.preview()

Count who a broadcast to some audiences would reach

Scopesaudiences:read
Signature
def preview(    body: BroadcastPreviewInput,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastPreviewResource

Returns the numbers send would work from, without sending or writing anything: recipients, the people a broadcast to these audiences would reach now, unsubscribed, the contacts skipped because they have unsubscribed, and suppressed, the subscribed contacts skipped because their address is on the suppression list. A contact in several of the audiences counts once.

The count is taken at the moment of the call, so contacts who join or leave before a send change it. Only audienceIds, excludeAudienceIds and segmentId are sent, so you can pass the same dict you are about to give send.

Parameters

body['audienceIds']list[str]Required

1 to 10 audience ids. One that names no audience in the workspace is a 404 audience_not_found, and All contacts, which no broadcast can go to, is a 422 audience_is_everyone.

body['excludeAudienceIds']list[str]

Up to 10 audiences whose members are left out of the count, as send leaves them out.

body['segmentId']str

A saved segment that narrows the count to the contacts that match it, as send narrows the broadcast.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastPreviewResource with audienceIds, excludeAudienceIds and segmentId as sent, recipients, unsubscribed and suppressed.

Example

from nextmail.types import BroadcastCreate from nextmail import nextmail draft: BroadcastCreate = {    'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71', 'aud_7e3d9a1c5b2f84e06d9a3c51'],    'from': 'Acme <[email protected]>',    'subject': 'The September release',    'text': 'Hi {{firstName|there}}, here is what changed this month.',} reach = nextmail.broadcasts.preview(draft)skipped = reach['unsubscribed'] + reach['suppressed']print(f'{reach["recipients"]} will get it, {skipped} skipped') if reach['recipients'] > 0:    nextmail.broadcasts.send(draft)

Notes

  • Needs only audiences:read, so a key that cannot send can still show somebody the count.

  • The SDK retries it after a network failure or a retryable status, since it changes nothing.

Also available in

API
POST /broadcasts/preview
TypeScript
nextmail.broadcasts.preview()
Ruby
broadcasts.preview
PHP
broadcasts->preview
Go
Broadcasts.Preview
Java
broadcasts().preview
C#
Broadcasts.PreviewAsync
CLI
openemail broadcasts preview

broadcasts.send()

Send one message to everybody in one or more audiences

Scopesemails:sendaudiences:read
Signature
def send(    body: BroadcastCreate,    *,    idempotency_key: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> SentBroadcastResource

Creates a broadcast: one message sent to every contact in the audiences you name, as a separate copy for each person. Every copy has exactly one recipient and no cc or bcc, so nobody sees who else it went to, and every copy is an ordinary email with its own msg_ id, events, tracking and webhooks. emails.list(broadcast_id=...) lists them. Copies are not filed in the Sent folder, because the broadcast is the record.

The call returns straight away with the broadcast queued, or scheduled when you pass scheduledAt, and the sending happens in the background, 50 people at a time. Poll get to follow status and counts as it goes.

Who gets it: every contact in at least one of audienceIds, counted once however many of them hold it, except a person who has unsubscribed and an address on the suppression list. audienceIds cannot name All contacts, the built-in audience of everyone the workspace has written to, because most of them never asked for its mail: that is a 422 audience_is_everyone. A contact added to one of the audiences after this call but before the sending reaches it is included. counts.recipients is the estimate taken at the call, and preview returns the same count without sending.

subject, html and text take merge fields in double braces, filled in from each contact: firstName, lastName, name, email and unsubscribeUrl. Each takes a fallback after a bar, so {{firstName|there}} reads there for a contact with no name. The first name is the first word of the contact's name and the last name is the rest. A custom field of the contact is written {{attributes.plan}} for the key plan, takes a fallback the same way, and reads as nothing when the contact has no such field and no fallback is given. Values are escaped in html, and any other {{…}} is left as written. With template, those five values are passed as props, but only the ones the template declares, except unsubscribeUrl, which every template receives.

Every copy carries the one-click unsubscribe headers mail clients and the large mailbox providers look for. A body or a template that does not place the unsubscribeUrl merge field itself gets a short footer with the link, in English, which also names the sender and shows the postal address once marketingPostalAddress is set through settings.update. Following the link unsubscribes the person from all of the workspace's marketing mail: every audience they are in shows unsubscribedAt, suppressions.list_unsubscribes lists them, and later broadcasts and automation emails skip them, even after they are added to an audience again. Their contact and mail sent to them one message at a time are not affected.

The whole send is checked against the plan's monthly sends before anything is written. A broadcast the allowance cannot cover raises a 429 send_quota_exceeded and leaves nothing behind, and each copy counts as one send.

Parameters

body['audienceIds']list[str]Required

1 to 10 audience ids such as aud_9f2c4b7e1a0d63d84c5f2e7b. A contact in several of them gets one copy. An id that names no audience in the workspace is a 404 audience_not_found and All contacts is a 422 audience_is_everyone, and either way nothing is sent.

body['excludeAudienceIds']list[str]

Up to 10 audiences whose members are left out, even when they are in one of audienceIds, such as the customers who already bought. All contacts may be named here.

body['segmentId']str

A saved segment, from segments.create, that narrows the audiences: only the contacts that match its conditions get a copy. Its conditions are read when the broadcast is sent or scheduled and kept with it. An id that names no segment is a 404 segment_not_found.

body['from']RecipientInputRequired

Sender as [email protected], Acme <[email protected]> or a dict such as {'email': '[email protected]', 'name': 'Acme'}. It must be an address the key may send as, otherwise 403 from_address_forbidden.

body['replyTo']RecipientInput

Where replies go, the same for every copy, in any of the forms from takes.

body['subject']str

Required unless a template supplies it, at most 998 characters. Takes merge fields.

body['html']str

HTML body, at most 1,000,000 characters, with merge fields. Without the unsubscribeUrl merge field in it, an unsubscribe footer is added.

body['text']str

Plain text body, at most 1,000,000 characters, with merge fields. Without the unsubscribeUrl merge field in it, an unsubscribe line is added.

body['template']BroadcastTemplateInput

A stored template instead of html and text, never beside them: a dict with id and, optionally, version, props and slots. The merge values reach it as props it declares, such as firstName and unsubscribeUrl.

body['tracking']TrackingRequest

Open and link tracking for every copy, a dict with opens and clicks. A field left out follows the address it is sent from when that address set it, and is on otherwise.

body['tags']dict[str, str]

Up to 8 tags as a dict, keys of 1 to 64 letters, digits, _ or -, values up to 256 characters. Every copy carries them, plus broadcast_id, which the server adds.

body['utm']BroadcastUtm

Campaign tags added to every http and https link in the body, a dict with source, medium, campaign, term and content, sent as utm_source and the rest. A link that already carries one keeps its own, and the unsubscribe link never gets them.

body['abTest']BroadcastAbTest

An A/B test, a dict with variants, 2 to 4 dicts that each replace the subject, or the html and or text, of the broadcast, so {} keeps it, testPercent (5 to 50, 20 unless set), the share of people that get a variant, metric, opens or clicks, which picks the winner, and waitMinutes (30 to 4320, 240 unless set) before it is picked. Everybody else then gets the winning variant, and a template broadcast can only vary the subject.

body['scheduledAt']datetime | str

A datetime, an ISO 8601 instant or a duration such as PT2H or P1D, in the future and at most 365 days out. Left out, the sending starts straight away.

idempotency_keystr

Your own key, 1 to 255 characters of letters, digits, _, ., : or -. Left out, the SDK makes one for the call, so its own retries never send twice. Sending the same key again returns the broadcast it created instead of a new one, and the same key with a different body is a 422 idempotency_key_reuse.

api_keystr

Sends with this key instead of the client's.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

SentBroadcastResource, a BroadcastResource plus replayed, with id (brd_ plus 24 hex), status queued or scheduled, audienceIds, from, subject, scheduledAt, createdAt and counts, whose recipients is the estimate and whose other counts start at 0.

Example

from datetime import datetime, timedelta, timezone from nextmail import nextmail broadcast = nextmail.broadcasts.send(    {        'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],        'from': 'Acme <[email protected]>',        'subject': '{{firstName|Hello}}, the September release is out',        'html': '<p>Hi {{firstName|there}}, here is what changed this month.</p>'        '<p><a href="{{unsubscribeUrl}}">Unsubscribe</a></p>',        'tags': {'campaign': 'release-2026-09'},        'scheduledAt': datetime.now(timezone.utc) + timedelta(hours=2),    },    idempotency_key='release-2026-09',) print(broadcast['id'], broadcast['status'], broadcast['replayed'])

Notes

  • Safe to retry. Every call carries an Idempotency-Key, yours or one the SDK makes, so a retry after a network failure returns the broadcast the first attempt created, with replayed set to True, instead of sending again. Keys are scoped to the API key or app that sent them. Reusing a key with a different body is a 422 idempotency_key_reuse.

  • Refusals: 422 no_recipients when the audiences are empty or everybody in them has unsubscribed or is suppressed, 422 audience_is_everyone when one of them is All contacts, 409 domain_not_sendable when the from domain cannot sign mail yet, 422 on template.* when the template does not resolve. Each raises NextmailApiError with that code.

  • No attachments, cc, bcc, translation or encryption. A body from html or text and a template together are refused.

  • A test key (oe_test_) creates a broadcast whose copies are marked sent and delivered to nobody, like any test send.

Also available in

API
POST /broadcasts
TypeScript
nextmail.broadcasts.send()
Ruby
broadcasts.send
PHP
broadcasts->send
Go
Broadcasts.Send
Java
broadcasts().send
C#
Broadcasts.SendAsync
CLI
openemail broadcasts send

broadcasts.list()

List one page of broadcasts, newest first

Scopesemails:readPages through results
Signature
def list(    *,    limit: int | None = None,    cursor: str | None = None,    audience_id: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Page[BroadcastResource]

Returns one page of the broadcasts in the workspace, newest first, each with live counts. audience_id= keeps the ones that were sent to that audience, alone or beside others.

Paging is keyset: limit= takes 1 to 100 and defaults to 25, and nextCursor, the id of the last broadcast on the page, goes back as cursor= while hasMore is True. Send the same audience_id= with every page. list_all and iterate do that walk for you.

The individual messages are not here. List the copies of one broadcast with emails.list(broadcast_id=...).

Parameters

limitint

Rows per page, a whole number from 1 to 100. The server defaults to 25.

cursorstr

The nextCursor from the previous page, a broadcast id. One that names no broadcast in the workspace is a 400 invalid_cursor.

audience_idstr

Only the broadcasts that included this audience. An id that names no audience returns an empty page.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Page[BroadcastResource], a dict with items, hasMore and nextCursor. Each item has id, status, audienceIds, from, subject, counts and the timestamps.

Example

from nextmail import nextmail page = nextmail.broadcasts.list(audience_id='aud_4c1b8e2a7d9f05c36b4e8a71', limit=10) for broadcast in page['items']:    counts = broadcast['counts']    print(broadcast['subject'], broadcast['status'], f'{counts["sent"]}/{counts["recipients"]}') if page['hasMore']:    print('next page starts after', page['nextCursor'])

Notes

  • A key limited to particular addresses or domains lists only the broadcasts sent from an address or domain it holds.

Also available in

API
GET /broadcasts
TypeScript
nextmail.broadcasts.list()
Ruby
broadcasts.list
PHP
broadcasts->list
Go
Broadcasts.List
Java
broadcasts().list
C#
Broadcasts.ListAsync
CLI
openemail broadcasts list

broadcasts.list_all()

Collect every broadcast into one list

Scopesemails:readPages through results
Signature
def list_all(    *,    limit: int | None = None,    cursor: str | None = None,    audience_id: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> builtins.list[BroadcastResource]

Follows nextCursor from page to page and returns every broadcast in the workspace as one list, newest first, or every one sent to audience_id= when you pass it. limit= sets the page size of each request, not the total.

Parameters

limitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

cursorstr

A broadcast id to start after.

audience_idstr

Only the broadcasts that included this audience.

api_keystr

Overrides the client's API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

list[BroadcastResource] holding every broadcast across all pages.

Example

from nextmail import nextmail broadcasts = nextmail.broadcasts.list_all(limit=100) sent = sum(broadcast['counts']['sent'] for broadcast in broadcasts) print(f'{len(broadcasts)} broadcasts, {sent} copies sent')

Notes

  • A failure on any page raises, and the broadcasts already fetched are discarded.

  • Every row carries live counts, so collecting a long history reads the copies of every broadcast in it. Use iterate to stop early.

  • A key limited to particular addresses or domains lists only the broadcasts sent from an address or domain it holds.

Also available in

API
GET /broadcasts
TypeScript
nextmail.broadcasts.listAll()
Ruby
broadcasts.list_all
PHP
broadcasts->listAll
Go
Broadcasts.ListAll
Java
broadcasts().listAll
C#
Broadcasts.ListAllAsync

broadcasts.iterate()

Stream broadcasts one at a time, newest first

Scopesemails:readPages through results
Signature
def iterate(    *,    limit: int | None = None,    cursor: str | None = None,    audience_id: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Iterator[BroadcastResource]

Returns a generator that yields broadcasts one at a time, newest first, and requests the next page only once the current one is used up. Breaking out of the loop stops the requests, so this is the cheap way to find the latest broadcast that matches something the filters cannot express.

Parameters

limitint

Page size per request, from 1 to 100, defaulting to 25 on the server.

cursorstr

A broadcast id to start after.

audience_idstr

Only the broadcasts that included this audience.

api_keystr

Overrides the client's API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Iterator[BroadcastResource], a generator yielding one broadcast per step.

Example

from nextmail import nextmail for broadcast in nextmail.broadcasts.iterate():    if broadcast['status'] == 'sending':        print('still going:', broadcast['id'], broadcast['counts']['queued'], 'waiting')        break

Notes

  • The generator is lazy, so an abandoned loop costs only the pages it read.

  • A key limited to particular addresses or domains lists only the broadcasts sent from an address or domain it holds.

Also available in

API
GET /broadcasts
TypeScript
nextmail.broadcasts.iterate()
Ruby
broadcasts.iterate
PHP
broadcasts->iterate
Go
Broadcasts.Iterate
Java
broadcasts().iterate
C#
Broadcasts.IterateAsync

broadcasts.get()

Read one broadcast and how far it has got

Scopesemails:read
Signature
def get(    id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Returns one broadcast with counts read live from its copies, which makes this the call to poll while it sends.

status moves from scheduled or queued to sending and settles on sent once every copy handed over has gone out or failed. It reads sending for as long as copies are still waiting, even after the last person was reached and completedAt was set. cancelled and failed are the other two ends, and on failed lastError says why: the from address can no longer be sent from, the template stopped resolving, the plan ran out part way, the sending itself kept failing, or not one copy could be written.

counts.recipients is the estimate taken when it was created. created is the copies written, one per person reached, skipped the people passed over because their address was suppressed by then, and failedToQueue the people whose copy could not be written. queued, sending, sent, failed and cancelled count the copies by the state each one is in now.

Parameters

idstrRequired

A brd_ id from send or list.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource with id, status, mode, source, audienceIds, from, subject, counts, lastError, scheduledAt, startedAt, completedAt, cancelledAt, createdAt and updatedAt.

Example

import time from nextmail import nextmail broadcast = nextmail.broadcasts.get('brd_5a8c1e3f7b2d94a06c8e1f3b') for _ in range(30):    if broadcast['status'] in ('sent', 'cancelled', 'failed'):        break     time.sleep(5)    broadcast = nextmail.broadcasts.get(broadcast['id'])    counts = broadcast['counts']    print(f'{counts["sent"]} of {counts["recipients"]} sent') print(broadcast['status'], broadcast['lastError'])

Notes

  • A missing broadcast raises NextmailApiError with is_not_found set and code broadcast_not_found. An id from another workspace is a 404 too.

  • For who each copy went to and what happened to it, use list_recipients, get_recipient reads one copy with its content, and stats sums it up.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}
TypeScript
nextmail.broadcasts.get()
Ruby
broadcasts.get
PHP
broadcasts->get
Go
Broadcasts.Get
Java
broadcasts().get
C#
Broadcasts.GetAsync
CLI
openemail broadcasts get

broadcasts.stats()

Read how a broadcast performed

Scopesemails:read
Signature
def stats(    id: str,    *,    grain: TrackingGrain | None = None,    offset_minutes: int | None = None,    days: int | None = None,    minutes: int | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastStatsResource

Returns the totals and a series for one broadcast. totals counts copies sent, delivered, bounced, complained (reported as spam) and failed, pending for the ones still waiting, and people who opened, clicked and unsubscribed, with opens and clicks as event counts. series is sparse and oldest first: one bucket per grain in which something happened, counting each person once at the first time it happened to them, so it adds up to the totals.

Pass days= or minutes= to also read what happened lately. window then counts the copies delivered, bounced, reported as spam, opened, clicked and unsubscribed inside it, and series keeps only its buckets, while totals still covers the whole broadcast. Without either, window is None.

Parameters

idstrRequired

A brd_ id from send or list.

grainTrackingGrain

Bucket width: minute, hour or day, defaulting to hour.

offset_minutesint

Minutes east of UTC to cut the buckets in, from -840 to 840. Pass time.localtime().tm_gmtoff // 60 for the local zone.

daysint

Reads a window of this many days too, from 1 to 1095. It starts at the beginning of its first grain bucket and ends now.

minutesint

The window in minutes, from 1 to 1576800, which wins over days= when both are sent.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastStatsResource with broadcastId, grain, totals, window and series, each bucket a dict with bucket, delivered, opened, clicked and unsubscribed. window holds since, delivered, bounced, complained, opened, clicked and unsubscribed, or is None when no window was asked for. links lists the links clicked most, clients the mail clients and countries the countries people opened it from, and abTest how each variant of an A/B test did and which one won, or None without a test.

Example

from nextmail import nextmail stats = nextmail.broadcasts.stats('brd_5a8c1e3f7b2d94a06c8e1f3b', grain='day') totals = stats['totals']rate = totals['opened'] / totals['sent'] if totals['sent'] else 0 print(f'{rate:.0%} opened, {totals["clicks"]} clicks, {totals["unsubscribed"]} unsubscribed')

Notes

  • Read only, so the SDK retries it after a network failure like any other read.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}/stats
TypeScript
nextmail.broadcasts.stats()
Ruby
broadcasts.stats
PHP
broadcasts->stats
Go
Broadcasts.Stats
Java
broadcasts().stats
C#
Broadcasts.StatsAsync
CLI
openemail broadcasts stats

broadcasts.analytics()

Read how broadcasts did over a time window

Scopesemails:read
Signature
def analytics(    *,    broadcast_ids: Sequence[str] | None = None,    days: int | None = None,    minutes: int | None = None,    grain: TrackingGrain | None = None,    offset_minutes: int | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastAnalyticsResource

Returns the numbers behind the Analytics tab of the Broadcasts page in one request: how the live broadcasts sent inside a window did, added up in totals and cut to grain in series, and one row per broadcast in broadcasts so they can be compared.

A copy counts when it was sent inside the window, and everything that happened to it afterwards counts with it, so an open today of a copy sent last week is in a 30 day window but not in a 1 day one. Test mode broadcasts are left out. totals and series add up the broadcasts named in broadcast_ids=, or every one when it is left out, while broadcasts always lists every broadcast in the window, newest first.

series is sparse and oldest first: a bucket in which nothing happened has no entry, so a chart must fill the gaps. It counts each person once, at the first time it happened to them. grain= sets the bucket width and the key shape, YYYY-MM-DD, YYYY-MM-DDTHH or YYYY-MM-DDTHH:MM, and offset_minutes= shifts the boundaries so days break where the reader's day does. The window starts at the beginning of its oldest bucket, reported as since, and ends now, reported as until.

Parameters

broadcast_idsSequence[str]

Up to 50 broadcast ids for totals and series to add up, sent comma separated. Leave it out, or pass an empty list, for every broadcast in the window. An id with no copy in the window adds nothing, and more than 50 is a 422.

daysint

Window length in days, from 1 to 1095, defaulting to 30.

minutesint

Window length in minutes, from 1 to 1576800, which wins over days= when both are sent.

grainTrackingGrain

Bucket width: minute, hour or day, defaulting to day.

offset_minutesint

Minutes east of UTC to cut the buckets in, from -840 to 840, defaulting to 0. Pass time.localtime().tm_gmtoff // 60 for the local zone.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastAnalyticsResource, a dict with object set to broadcast_analytics, since, until, grain, offsetMinutes, broadcastIds, totals, series and broadcasts. totals has broadcasts and the counts BroadcastStatsTotals has, each entry of series has bucket, sent, delivered, opened, clicked and unsubscribed, and each entry of broadcasts has id, subject, status, sentAt and the same counts.

Example

import time from nextmail import nextmail analytics = nextmail.broadcasts.analytics(    days=90, offset_minutes=time.localtime().tm_gmtoff // 60) totals = analytics['totals']rate = totals['opened'] / totals['delivered'] if totals['delivered'] else 0print(f'{totals["broadcasts"]} broadcasts, {rate:.0%} opened') for row in analytics['broadcasts']:    print(row['subject'], row['sent'], row['opened'], row['clicked'])

Notes

  • Read only, so the SDK retries it after a network failure like any other read.

  • opened, clicked and unsubscribed count people, while opens and clicks count events.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds, and the rest are left out as if they did not exist.

Also available in

API
GET /broadcasts/analytics
TypeScript
nextmail.broadcasts.analytics()
Ruby
broadcasts.analytics
PHP
broadcasts->analytics
Go
Broadcasts.Analytics
Java
broadcasts().analytics
C#
Broadcasts.AnalyticsAsync
CLI
openemail broadcasts analytics

broadcasts.list_recipients()

List who a broadcast went to and what happened to each copy

Scopesemails:readPages through results
Signature
def list_recipients(    id: str,    *,    limit: int | None = None,    cursor: str | None = None,    filter: BroadcastRecipientFilter | None = None,    q: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Page[BroadcastRecipientResource]

Returns one page of the people a broadcast went to, one row per copy, sorted by address. Each row says the copy's status, when it was sent and delivered, whether it bounced or was reported as spam, how often it was opened and clicked, and whether the person unsubscribed from one of the broadcast's audiences after it went out.

Opens and clicks leave out image proxies and link scanners, and stay 0 when the broadcast went out with tracking off. emailId is the copy's msg_ id, which get_recipient reads with its content and emails.get reads as a sent email.

Parameters

idstrRequired

A brd_ id from send or list.

limitint

Page size, from 1 to 200. The server defaults to 50.

cursorstr

The nextCursor of the previous page. Send the same filter= and q= with it.

filterBroadcastRecipientFilter

Keeps one group: pending, sent, delivered, opened, not_opened (sent and never opened), clicked, bounced, complained, failed (failed or cancelled) or unsubscribed. BROADCAST_RECIPIENT_FILTERS names them.

qstr

Searches the address and the name, ignoring case.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Page[BroadcastRecipientResource], a dict with items, hasMore and nextCursor. Each item has emailId, contactId, email, name, status, sentAt, deliveredAt, bouncedAt, complainedAt, failure, opens, firstOpenAt, clicks, firstClickAt and unsubscribedAt.

Example

from nextmail import nextmail page = nextmail.broadcasts.list_recipients('brd_5a8c1e3f7b2d94a06c8e1f3b', filter='bounced') for recipient in page['items']:    print(recipient['email'], recipient['bouncedAt'], recipient['failure'])

Notes

  • A missing broadcast raises NextmailApiError with code broadcast_not_found.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}/recipients
TypeScript
nextmail.broadcasts.listRecipients()
Ruby
broadcasts.list_recipients
PHP
broadcasts->listRecipients
Go
Broadcasts.ListRecipients
Java
broadcasts().listRecipients
C#
Broadcasts.ListRecipientsAsync
CLI
openemail broadcasts list-recipients

broadcasts.list_all_recipients()

Collect everybody a broadcast went to into one list

Scopesemails:readPages through results
Signature
def list_all_recipients(    id: str,    *,    limit: int | None = None,    cursor: str | None = None,    filter: BroadcastRecipientFilter | None = None,    q: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> builtins.list[BroadcastRecipientResource]

Walks every page of list_recipients and returns every copy of the broadcast as one list, sorted by address. One request per page, with the same filter= and q= on each.

Parameters

idstrRequired

A brd_ id from send or list.

limitint

Page size for each request, from 1 to 200. The server defaults to 50.

cursorstr

Starts the walk after this cursor instead of the first page.

filterBroadcastRecipientFilter

Keeps one group: pending, sent, delivered, opened, not_opened (sent and never opened), clicked, bounced, complained, failed (failed or cancelled) or unsubscribed. BROADCAST_RECIPIENT_FILTERS names them.

qstr

Searches the address and the name, ignoring case.

api_keystr

Overrides the client's API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

list[BroadcastRecipientResource] holding every copy that matches.

Example

from nextmail import nextmail unopened = nextmail.broadcasts.list_all_recipients(    'brd_5a8c1e3f7b2d94a06c8e1f3b', filter='not_opened') print(len(unopened), [recipient['email'] for recipient in unopened])

Notes

  • If any page fails, the call raises and the rows already fetched are discarded.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}/recipients
TypeScript
nextmail.broadcasts.listAllRecipients()
Ruby
broadcasts.list_all_recipients
PHP
broadcasts->listAllRecipients
Go
Broadcasts.ListAllRecipients
Java
broadcasts().listAllRecipients
C#
Broadcasts.ListAllRecipientsAsync

broadcasts.iterate_recipients()

Walk everybody a broadcast went to, one copy at a time

Scopesemails:readPages through results
Signature
def iterate_recipients(    id: str,    *,    limit: int | None = None,    cursor: str | None = None,    filter: BroadcastRecipientFilter | None = None,    q: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Iterator[BroadcastRecipientResource]

Returns a generator that yields every copy of the broadcast one at a time, sorted by address, fetching the next page only when the loop asks for it. Breaking out of the loop stops the requests.

Parameters

idstrRequired

A brd_ id from send or list.

limitint

Page size for each request, from 1 to 200. The server defaults to 50.

cursorstr

Starts after this cursor instead of the first page.

filterBroadcastRecipientFilter

Keeps one group: pending, sent, delivered, opened, not_opened (sent and never opened), clicked, bounced, complained, failed (failed or cancelled) or unsubscribed. BROADCAST_RECIPIENT_FILTERS names them.

qstr

Searches the address and the name, ignoring case.

api_keystr

Overrides the client's API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Iterator[BroadcastRecipientResource], a generator yielding one copy per step.

Example

from nextmail import nextmail clicked = nextmail.broadcasts.iterate_recipients(    'brd_5a8c1e3f7b2d94a06c8e1f3b', filter='clicked') for recipient in clicked:    print(recipient['email'], recipient['clicks'], recipient['firstClickAt'])

Notes

  • Memory stays flat however large the broadcast, because only one page is held at a time.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}/recipients
TypeScript
nextmail.broadcasts.iterateRecipients()
Ruby
broadcasts.iterate_recipients
PHP
broadcasts->iterateRecipients
Go
Broadcasts.IterateRecipients
Java
broadcasts().iterateRecipients
C#
Broadcasts.IterateRecipientsAsync

broadcasts.get_recipient()

Read one person's copy of a broadcast

Scopesemails:read
Signature
def get_recipient(    id: str,    email_id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastRecipientContentResource

Returns one copy: the same row list_recipients gives, plus the subject, html and text exactly as that person received them, with the merge fields filled in and their own unsubscribe link. The HTML is from before open and click tracking was added.

Parameters

idstrRequired

A brd_ id from send or list.

email_idstrRequired

The emailId of the copy, from list_recipients.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastRecipientContentResource: emailId, contactId, email, name, status, sentAt, deliveredAt, bouncedAt, complainedAt, failure, opens, firstOpenAt, clicks, firstClickAt and unsubscribedAt, plus subject, html and text.

Example

from nextmail import NextmailApiError, nextmail try:    copy = nextmail.broadcasts.get_recipient(        'brd_5a8c1e3f7b2d94a06c8e1f3b', 'msg_01dad25067bc4dac966d515d'    )except NextmailApiError as error:    if error.is_not_found:        print('No such copy:', error.code)    else:        raiseelse:    print(copy['subject'], copy['deliveredAt'], copy['opens'])

Notes

  • An email_id that is not a copy of this broadcast raises NextmailApiError with code recipient_not_found.

  • A key limited to particular addresses or domains reads only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
GET /broadcasts/{id}/recipients/{emailId}
TypeScript
nextmail.broadcasts.getRecipient()
Ruby
broadcasts.get_recipient
PHP
broadcasts->getRecipient
Go
Broadcasts.GetRecipient
Java
broadcasts().getRecipient
C#
Broadcasts.GetRecipientAsync
CLI
openemail broadcasts get-recipient

broadcasts.cancel()

Stop a broadcast that is scheduled, queued or still sending

Scopesemails:send
Signature
def cancel(    id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Stops a broadcast that is scheduled, queued or sending, including one that has reached everybody while some copies are still waiting to go. Nobody else is added, and every copy still waiting is cancelled. A copy already being handed over finishes, and copies that have gone cannot be recalled, so counts.sent keeps them and counts.cancelled shows what was stopped.

Once every copy has gone out there is nothing left to stop, and the call raises a 409 broadcast_not_cancellable. A failed broadcast with copies still waiting can be cancelled to stop them, and one with nothing waiting is refused the same way. Cancelling a broadcast that is already cancelled returns it as it stands, so the call is safe to repeat, and the SDK retries it after a network failure.

Parameters

idstrRequired

The brd_ id to cancel.

api_keystr

Cancels with this key instead of the client's.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource in its new state, status cancelled with cancelledAt set and the copies counted by where each one ended up.

Example

from nextmail import nextmail scheduled = nextmail.broadcasts.send(    {        'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],        'from': '[email protected]',        'subject': 'Doors open on Friday',        'text': 'Hi {{firstName|there}}, doors open at nine.',        'scheduledAt': 'P1D',    }) cancelled = nextmail.broadcasts.cancel(scheduled['id']) print(cancelled['status'], cancelled['cancelledAt'], cancelled['counts']['cancelled'])

Notes

  • Cancelling a scheduled broadcast before scheduledAt sends nothing at all.

  • A cancelled broadcast stays cancelled. There is no resume, so send again to the audiences that still need it.

  • A key limited to particular addresses or domains cancels only broadcasts sent from an address or domain it holds. Any other id raises broadcast_not_found, as if the broadcast did not exist.

Also available in

API
POST /broadcasts/{id}/cancel
TypeScript
nextmail.broadcasts.cancel()
Ruby
broadcasts.cancel
PHP
broadcasts->cancel
Go
Broadcasts.Cancel
Java
broadcasts().cancel
C#
Broadcasts.CancelAsync
CLI
openemail broadcasts cancel

broadcasts.create_draft()

Save a broadcast as a draft without sending it

Scopesemails:sendaudiences:read
Signature
def create_draft(    body: BroadcastDraftCreate,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Saves a broadcast with status draft. Nothing is sent, nothing counts against the plan and the audiences may still be empty, so a draft can be written over several sittings. update changes it, send_draft sends it or schedules it, and get returns what it holds in content.

The dict takes the keys send takes, except scheduledAt, which belongs to send_draft. Only from is required. The sender, the audiences and a template are checked as they are saved.

Parameters

body['from']RecipientInputRequired

Sender as [email protected], Acme <[email protected]> or a dict with email and name. It must be an address the key may send as, otherwise 403 from_address_forbidden.

body['audienceIds']list[str]

Up to 10 audience ids. Left out or empty, the draft has no audience yet and send_draft refuses it until update gives it one.

body['excludeAudienceIds']list[str]

Up to 10 audiences whose members are left out, even when they are in one of audienceIds.

body['segmentId']str

A saved segment that narrows the audiences when the draft is sent. It has to exist when the draft is saved and again when it is sent.

body['replyTo']RecipientInput

Where replies go, the same for every copy.

body['subject']str

At most 998 characters, with merge fields. Needed before sending unless a template supplies it.

body['html']str

HTML body, at most 1,000,000 characters, with merge fields.

body['text']str

Plain text body, at most 1,000,000 characters, with merge fields.

body['template']BroadcastTemplateInput

A stored template instead of html and text: a dict with id and, optionally, version, props and slots.

body['tracking']TrackingRequest

Open and link tracking for every copy, a dict with opens and clicks.

body['tags']dict[str, str]

Up to 8 tags every copy carries.

body['utm']BroadcastUtm

Campaign tags added to every http and https link in the body, a dict with source, medium, campaign, term and content. A link that already carries one keeps its own, and the unsubscribe link never gets them.

body['abTest']BroadcastAbTest

An A/B test, as send takes it.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource with status draft, counts at 0 and the saved fields in content.

Example

from nextmail import nextmail draft = nextmail.broadcasts.create_draft(    {        'from': 'Acme <[email protected]>',        'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],        'subject': 'The October release',        'text': 'Hi {{firstName|there}}, here is what changed this month.',        'utm': {'source': 'newsletter', 'medium': 'email', 'campaign': 'october-release'},    }) print(draft['id'], draft['status'])

Notes

Also available in

API
POST /broadcasts/drafts
TypeScript
nextmail.broadcasts.createDraft()
Ruby
broadcasts.create_draft
PHP
broadcasts->createDraft
Go
Broadcasts.CreateDraft
Java
broadcasts().createDraft
C#
Broadcasts.CreateDraftAsync
CLI
openemail broadcasts create-draft

broadcasts.update()

Change a draft, or a scheduled broadcast before it starts

Scopesemails:sendaudiences:read
Signature
def update(    id: str,    patch: BroadcastUpdate,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

A partial update: a key you leave out keeps what it holds, and None clears replyTo, html, text, template or utm. Sending any of html, text or template replaces the body as a whole.

A draft takes every change and stays a draft. A scheduled broadcast takes changes until its sending starts, and each change is checked the way send checks a new one, so counts.recipients is counted again. scheduledAt moves a scheduled broadcast to another time. Once a broadcast is queued, sending or finished, the call raises a 409 broadcast_not_editable.

Parameters

idstrRequired

The brd_ id of a draft or of a scheduled broadcast.

patch['audienceIds']list[str]

The new audiences, replacing the old list, up to 10.

patch['excludeAudienceIds']list[str]

The new list of audiences whose members are left out.

patch['segmentId']str | None

A saved segment that narrows the audiences. None takes it away.

patch['from']RecipientInput

A new sender the key may send as.

patch['replyTo']RecipientInput | None

Where replies go. None takes it away.

patch['subject']str

A new subject, with merge fields.

patch['html']str | None

A new HTML body.

patch['text']str | None

A new plain text body.

patch['template']BroadcastTemplateInput | None

A stored template instead of html and text.

patch['tracking']TrackingRequest

Open and link tracking for every copy.

patch['tags']dict[str, str]

The new tags, replacing the old ones.

patch['utm']BroadcastUtm | None

New campaign tags for the links. None takes them away.

patch['abTest']BroadcastAbTest | None

A new A/B test, as send takes it. None takes it away.

patch['scheduledAt']datetime | str

A new start for a scheduled broadcast: a datetime, an ISO 8601 instant or a duration such as PT2H. A draft refuses it with 422 invalid_parameter, since send_draft schedules a draft.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource as it stands after the change, with content.

Example

from nextmail import nextmail draft = nextmail.broadcasts.create_draft({'from': '[email protected]', 'subject': 'Draft'}) updated = nextmail.broadcasts.update(    draft['id'],    {        'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],        'subject': 'Doors open on Friday',        'text': 'Hi {{firstName|there}}, doors open at nine.',    },) print(updated['status'], updated['subject'])

Notes

Also available in

API
PATCH /broadcasts/{id}
TypeScript
nextmail.broadcasts.update()
Ruby
broadcasts.update
PHP
broadcasts->update
Go
Broadcasts.Update
Java
broadcasts().update
C#
Broadcasts.UpdateAsync
CLI
openemail broadcasts update

broadcasts.send_draft()

Send a draft now, or schedule it

Scopesemails:sendaudiences:read
Signature
def send_draft(    id: str,    body: BroadcastSendDraftInput | None = None,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Turns a draft into a broadcast that goes out: queued straight away, or scheduled when you pass scheduledAt. The draft is checked the way send checks a new broadcast, so it needs an audience, a body, a subject or a template that supplies one, somebody to reach, and room in the plan for every copy.

A broadcast that is not a draft raises a 409 broadcast_not_draft, so calling it twice never sends twice.

Parameters

idstrRequired

The brd_ id of the draft.

body['scheduledAt']datetime | str

A datetime, an ISO 8601 instant or a duration such as PT2H, in the future and at most 365 days out. Left out, the sending starts straight away.

api_keystr

Sends with this key instead of the client's.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource with status queued or scheduled and counts.recipients estimated at the call.

Example

from nextmail import nextmail draft = nextmail.broadcasts.create_draft(    {        'from': 'Acme <[email protected]>',        'audienceIds': ['aud_4c1b8e2a7d9f05c36b4e8a71'],        'subject': 'Doors open on Friday',        'text': 'Hi {{firstName|there}}, doors open at nine.',    }) sent = nextmail.broadcasts.send_draft(draft['id'], {'scheduledAt': 'PT2H'}) print(sent['status'], sent['scheduledAt'], sent['counts']['recipients'])

Notes

Also available in

API
POST /broadcasts/{id}/send
TypeScript
nextmail.broadcasts.sendDraft()
Ruby
broadcasts.send_draft
PHP
broadcasts->sendDraft
Go
Broadcasts.SendDraft
Java
broadcasts().sendDraft
C#
Broadcasts.SendDraftAsync
CLI
openemail broadcasts send-draft

broadcasts.duplicate()

Copy a broadcast into a new draft

Scopesemails:sendaudiences:read
Signature
def duplicate(    id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Makes a new draft from any broadcast: the same sender, audiences, left-out audiences, subject, body or template, tracking, tags and campaign tags. The time it was scheduled for is not copied. The original is not changed, and nothing is sent until send_draft.

Parameters

idstrRequired

The brd_ id to copy.

api_keystr

Overrides the client's API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource for the new draft, with its own brd_ id, status draft and content.

Example

from nextmail import nextmail copy = nextmail.broadcasts.duplicate('brd_5e8a2c1f9b3d74e06a5c2f81') print(copy['id'], copy['status'])

Notes

Also available in

API
POST /broadcasts/{id}/duplicate
TypeScript
nextmail.broadcasts.duplicate()
Ruby
broadcasts.duplicate
PHP
broadcasts->duplicate
Go
Broadcasts.Duplicate
Java
broadcasts().duplicate
C#
Broadcasts.DuplicateAsync
CLI
openemail broadcasts duplicate

broadcasts.resend()

Send a broadcast again to the people who did not open it

Scopesemails:sendaudiences:read
Signature
def resend(    id: str,    body: BroadcastResendInput | None = None,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastResource

Creates a new broadcast with the same content for the people the first one reached and who have not opened it, optionally under a new subject. Somebody who has unsubscribed since, or whose address is now suppressed, is skipped. The new broadcast carries content.resendOf, and counts against the plan like any send.

The first broadcast has to have finished going out and to have tracked opens. Otherwise the call raises a 409 resend_unavailable.

Parameters

idstrRequired

The brd_ id of the broadcast that went out.

body['subject']str

A new subject for the second try, with merge fields. Left out, the first subject is used.

body['scheduledAt']datetime | str

When to start, in the future and at most 365 days out. Left out, the sending starts straight away.

api_keystr

Sends with this key instead of the client's.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastResource for the new broadcast, queued or scheduled, with counts.recipients holding the people who had not opened.

Example

from nextmail import nextmail second = nextmail.broadcasts.resend(    'brd_5e8a2c1f9b3d74e06a5c2f81',    {'subject': 'In case you missed it: doors open on Friday', 'scheduledAt': 'P2D'},) print(second['id'], second['counts']['recipients'])

Notes

Also available in

API
POST /broadcasts/{id}/resend
TypeScript
nextmail.broadcasts.resend()
Ruby
broadcasts.resend
PHP
broadcasts->resend
Go
Broadcasts.Resend
Java
broadcasts().resend
C#
Broadcasts.ResendAsync
CLI
openemail broadcasts resend

broadcasts.send_test()

Send a test copy of a broadcast to yourself or a few people

Scopesemails:send
Signature
def send_test(    body: BroadcastTestInput,    *,    api_key: str | None = None,    timeout: float | None = None,) -> BroadcastTestResource

Sends one copy of a broadcast to up to five addresses, to read it before it goes to an audience. It goes to the account address of the person the key or app acts for, the workspace owner for an API key, unless to names others. The merge fields are filled from the contact that has the address when there is one and from a sample name otherwise, the subject starts with [Test], the unsubscribe footer is added as it would be and its link does nothing. Opens and clicks are not tracked, and utm is applied to the links.

No broadcast is created and nobody joins an audience. Each copy counts as one send.

Parameters

body['from']RecipientInputRequired

The sender, an address the key may send as.

body['to']list[RecipientInput]

1 to 5 addresses. Left out, the test goes to the account address of the person the key or app acts for.

body['replyTo']RecipientInput

Where replies go.

body['subject']str

Required unless a template supplies it, with merge fields.

body['html']str

HTML body with merge fields.

body['text']str

Plain text body with merge fields.

body['template']BroadcastTemplateInput

A stored template instead of html and text.

body['utm']BroadcastUtm

Campaign tags added to the links, as the broadcast would add them.

api_keystr

Sends with this key instead of the client's.

timeoutfloat

Seconds this call may take, the response included, before it raises NextmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

BroadcastTestResource with the msg_ id of each copy in emailIds and the addresses in to.

Example

from nextmail import nextmail test = nextmail.broadcasts.send_test(    {        'from': 'Acme <[email protected]>',        'to': ['[email protected]'],        'subject': '{{firstName|Hello}}, doors open on Friday',        'html': '<p>Hi {{firstName|there}}, doors open at nine.</p>',    }) print(test['emailIds'], test['to'])

Notes

  • The SDK does not retry it, since a retry would send the test again.

Also available in

API
POST /broadcasts/test
TypeScript
nextmail.broadcasts.sendTest()
Ruby
broadcasts.send_test
PHP
broadcasts->sendTest
Go
Broadcasts.SendTest
Java
broadcasts().sendTest
C#
Broadcasts.SendTestAsync
CLI
openemail broadcasts send-test