Skip to the documentation
Ruby

Audiences

`audiences.list`, `get`, `create`, `update`, `delete`, `empty`, `growth`, `list_contacts`, `add_contact`, `add_contacts`, `import_contacts`, `remove_contact` and `remove_contacts`.

Every method

audiences.rb
audiences = client.audiences.list_alleveryone = audiences.find { |audience| audience[:builtin] == "default" } list = client.audiences.create(  name: "Product updates",  description: "Customers who asked to hear about releases") client.contacts.create(email: "[email protected]", name: "Grace Hopper")client.audiences.add_contact(list[:id], email: "[email protected]") bulk = client.audiences.add_contacts(list[:id], emails: ["[email protected]", "[email protected]"]) imported = client.audiences.import_contacts(  list[:id],  contacts: [{email: "[email protected]", name: "Katherine Johnson"}]) members = client.audiences.list_all_contacts(list[:id], q: "grace", sort: "added-newest", limit: 200) growth = client.audiences.growth(audience_ids: [list[:id]], days: 30) client.audiences.update(list[:id], name: "Release notes")client.audiences.remove_contact(list[:id], "[email protected]")client.audiences.remove_contacts(list[:id], emails: ["[email protected]"])client.audiences.empty(list[:id])client.audiences.delete(list[:id]) puts everyone[:contactCount] if everyoneputs bulk[:missing], imported[:created], members.size, growth.dig(:totals, :added)

An audience is a named list of contacts in this workspace. Every contact is in the built-in default audience from the moment it exists, and builtin is what names that row. The rest are yours to make, fill and delete. Branch on builtin rather than on the name, which anybody can change.

A call on one audience takes its id as the first argument, and remove_contact takes the address as the second. Everything else is a Ruby keyword, and a request body can also be passed as one Hash. The options of growth and list_contacts are snake_case (audience_ids:, offset_minutes:), while the fields of a body keep the API’s names (emails:, contacts:). A response is a Hash with Symbol keys in the API’s camelCase, so audience[:contactCount] reads the count.

Send to one or more audiences with client.broadcasts.send, on the Broadcasts page. Putting a contact in an audience is a write to the audience rather than to the contact, so audiences:write is the only scope checked. import_contacts is the exception. It creates contacts, so it needs contacts:write as well.

add_contact takes an address that is already a contact and refuses one that is not, with 422 contact_not_found, raised as OpenEmail::ValidationError. Save it with client.contacts.create first. Adding somebody twice answers with the membership that is already there, carrying its original addedAt, so the call is safe to retry, and the gem retries it after a network failure.

The default audience can be renamed and described like any other, but it cannot be deleted and it cannot be thinned. Both are refused with 409 audience_immutable, raised as OpenEmail::ConflictError with conflict? true. Delete the contact when you mean the contact to go.

Response: an audience

list returns one page of these as an OpenEmail::Page, with items, has_more? and next_cursor, the default audience first and the rest newest first. A page holds 25 unless limit: asks for up to 100. list_all returns every page in one Array, and iterate yields one audience at a time to a block, or returns an Enumerator without one. get, create and update each return one audience. list_contacts returns a page of contacts instead, the contacts themselves with the date each one joined rather than membership records, with list_all_contacts and iterate_contacts beside it.

idString
The durable handle, `aud_` followed by 24 hex characters. Names are not unique, so this is what belongs in stored configuration.
nameString
Trimmed on write, 1 to 120 characters. Two audiences may share a name, because an audience is addressed by its id.
descriptionString or nil
Free text for whoever reads the list later. It is nil when nobody wrote any, and `description: nil` on `update` clears it.
builtinString or nil
`default` on exactly one row per workspace, the audience that holds every contact, and nil on every audience somebody created. Compare it with `"default"` rather than testing it for nil, so that a built-in added later is not mistaken for the default audience.
contactCountInteger
How many contacts are in the audience, counted at the moment of the read rather than cached. Two reads either side of a `contacts.create` disagree by one.
lastContactAtString or nil
ISO 8601 UTC, when the contact who joined most recently joined this audience. It is nil while the audience is empty.
createdAtString
ISO 8601 UTC, when the audience was made. It fixes the list order after the default one.
updatedAtString
ISO 8601 UTC, bumped by a rename or a description change. Membership changes do not touch it.

Parameters: audiences.list_contacts

limitInteger
How many contacts per page, a whole number from 1 to 200, defaulting to 50.
cursorString
The `next_cursor` from the previous page, sent with the same `q:`, `source:`, `sort:` and `statuses:`. A cursor naming a contact that is not in this audience is a 400 `invalid_cursor`, raised as `OpenEmail::InvalidRequestError`.
qString
Searches the name and the address, up to 200 characters. When nothing matches exactly on the first page, close spellings are returned instead, and the pages that follow keep matching the same way.
sourceString
`manual` for the contacts somebody saved on purpose, `auto` for the ones the app composer recorded. Leave it out for everyone in the audience.
sortString
`last-heard-newest` (the default) and `last-heard-oldest` go by `lastSeenAt`, and contacts that have never been mailed come last in the first and first in the second. `added-newest` and `added-oldest` go by when each contact joined this audience, and `name` ignores case and sorts a contact with no name by its address.
statusesArray<String>
`["subscribed"]` keeps the members who have not unsubscribed and `["unsubscribed"]` the ones who have. Leave it out, pass an empty Array, or name both, for everyone in the audience. `OpenEmail::AUDIENCE_MEMBER_STATUSES` holds the values, and the gem sends them joined with commas as the `status` query parameter.

Response: a contact in an audience

list_contacts returns an OpenEmail::Page of contact Hashes, and list_all_contacts and iterate_contacts walk every page with the same keywords. Each row is a contact in the shape contacts.list returns, whose fields are on the Contacts page, with two more. Walking every page is how to export an audience.

addedAtString
ISO 8601 UTC, when the contact joined this audience. Taking a contact out and adding it again starts it afresh.
unsubscribedAtString or nil
ISO 8601 UTC, when the contact unsubscribed from a broadcast sent to this audience, or nil while it is subscribed. An unsubscribed contact stays in the audience, and broadcasts to it skip it. Taking it out and adding it again makes it subscribed afresh.

Adding and removing in bulk

add_contacts and remove_contacts take emails:, an Array of 1 to 200 addresses, and change one audience in one request. add_contacts never creates a contact. An address that is not one comes back in missing, and import_contacts is the call that creates them. Both are safe to repeat, so the gem retries them after a network failure, and a retry reports the same people as already done rather than failing.

Adding to the default audience answers added: 0, because every contact is in it already, and remove_contacts on it is refused with 409 audience_immutable. Taking somebody out of an audience leaves them in the address book, in the default audience and in their other audiences.

audienceIdString
The audience the call changed, on both results.
addedInteger
On the `add_contacts` result: the new memberships this call made.
unchangedInteger
On the `add_contacts` result: contacts that were in the audience already. Nothing was written for them.
removedInteger
On the `remove_contacts` result: the memberships this call took away.
notInAudienceArray<String>
On the `remove_contacts` result: contacts that were not in the audience, so nothing happened to them.
missingArray<String>
On both: the addresses that are not contacts in this workspace, lowercased and without repeats.

Importing

import_contacts is the CSV import on the audience page. It takes contacts:, an Array of 1 to 500 Hashes, each with an email and an optional name. Each well-formed address becomes a contact if it is not one yet, and every one lands in the audience. Send a longer list in several calls. It needs audiences:write and contacts:write, and a key missing either is refused with 403 insufficient_scope, where scope_missing? is true on the error.

An address that is already a contact is reused and keeps its name, and a name here only fills one that was empty. A new contact is saved as manual and joins the default audience too, and an address that was deleted from the book comes back. Replaying the same rows creates nothing twice, so the gem retries the call after a network failure.

audienceIdString
The audience the rows went into.
createdInteger
New contacts this call saved.
addedInteger
New memberships in this audience, counting contacts that existed already and were not in it yet.
skippedInteger
Rows that were not imported because the address was malformed.
invalidArray<String>
The malformed addresses, exactly as they were sent.

Emptying

empty(id) takes every contact out of one audience in one request and returns the audience as it now stands, with contactCount at 0, plus removed, the number of memberships taken away. The audience keeps its id, name and description, and every contact stays in the address book and in its other audiences.

It cannot be undone and nothing records who was in the list, so walk list_all_contacts first if you may want it back. The default audience cannot be emptied, and the call is refused with 409 audience_immutable. The gem does not retry empty after a network failure, because a second call succeeds with removed: 0. If a response was lost, read the audience with get.

Growth

growth reads how many contacts joined each audience over a window that ends now, and how many unsubscribed inside it, by day, hour or minute. It is the chart on the audiences page. It takes keywords, needs audiences:read and returns one Hash.

audience_growth.rb
growth = client.audiences.growth(  audience_ids: ["aud_9f2c4b7e1a0d63d84c5f2e7b"],  days: 90,  grain: "day",  offset_minutes: Time.now.utc_offset / 60) puts "#{growth.dig(:totals, :added)} joins since #{growth[:since]}" growth[:series].each do |series|  puts "#{series[:name]}: #{series[:before]} before the window, #{series[:total]} now"end

An audience records when somebody joined and never when they left, so every figure counts the people still in the list today by the date they joined, and a line never falls. A contact who joined and later left is in none of the numbers.

Parameters

audience_idsArray<String>
Up to 50 audience ids, sent joined with commas. Leave it out, or pass an empty Array, for every audience. An id that is not an audience in this workspace is a 404 `audience_not_found`, and more than 50 is a 422.
daysInteger
How far back the window reaches, 1 to 1095. It is 30 when neither `days:` nor `minutes:` is given.
minutesInteger
The window in minutes, 1 to 1576800, for a window shorter than a day. It wins over `days:` when both are given.
grainString
The size of each bucket: `day` (the default), `hour` or `minute`.
offset_minutesInteger
The viewer’s offset from UTC in minutes, -840 to 840, so day and hour buckets start at their local boundary. 0 by default. `Time.now.utc_offset / 60` is the offset of the machine the code runs on.

Response

sinceString
ISO 8601 UTC, the start of the first bucket.
untilString
ISO 8601 UTC, the moment of the read.
totalsHash
`contacts` counts each person once however many lists they are in, and `memberships` adds the lists up, so a person counts once for every list read that holds them. `added` sums the joins in the window, `lists` is how many audiences were read, and `busiest` is the bucket with the most joins, or nil. `subscribed` counts each person still subscribed to at least one of the audiences read, and `unsubscribed` adds up the unsubscribes inside the window.
seriesArray<Hash>
One entry per audience, largest first and then by name: `id`, `name`, `builtin`, `total` members now, `subscribed` (those still subscribed), `before` (those who joined before `since`), `added` (those who joined inside the window), `unsubscribed` (those who unsubscribed inside it) and `buckets`, oldest first, each a Hash with `bucket`, `added` and `unsubscribed`. Here `builtin` is `true` on the default audience and `false` on the rest, not the String an audience Hash carries. Only buckets with a join or an unsubscribe are listed, keyed `YYYY-MM-DD`, `YYYY-MM-DDTHH` or `YYYY-MM-DDTHH:MM` in the offset’s local time.