Triggered Emails

Full reference for the Triggered Emails resource in the bluefox.email API. See the API overview for authentication, the response envelope, and pagination.

List triggered emails

GET /v1/projectId/{projectId}/triggered-emails

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
limitqueryintegerMax items per page. Capped at 30.
skipqueryintegerNumber of items to skip, for pagination.
sortquerystringField to sort by.
orderquerystring
filter[<field>]querystringExact-match filter on a top-level field, e.g. filter[name]=Welcome. Repeatable for multiple fields.

Responses

StatusDescription
200OK
403Missing or invalid API key

Response body

FieldTypeRequiredDescription
itemsarray
countintegerTotal matching items across all pages, not just this page.
nextstringFull URL (path + query string) of the next page, or null if this is the last page.

Create a triggered email

POST /v1/projectId/{projectId}/triggered-emails

subscriberListId must belong to this same project - a valid ID from a different project 404s.

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.

Request body

FieldTypeRequiredDescription
namestringyes
subscriberListIdstringyesRequired. If the user hasn't told you which list this triggered email is for, call GET /v1/projectId/{projectId}/subscriber-lists first and ask them to choose from the real list names - don't ask them for a raw ID.
excludeUnengagedboolean
subjectstringyes
previewTextstringOptional, but meaningfully affects open rates - if the user hasn't given you one, ask what they'd like it to say rather than leaving it blank.
senderIdentitystringA sender identity _id. If the user hasn't told you which one to use, call GET /v1/projectId/{projectId}/sender-identities first and ask them to choose from the real email addresses returned - don't ask them for a raw ID.
replyTostring
typestring (html | text)Omit to keep using the visual (Chamaileon) editor document. Set to "html" or "text" to author document as a raw Handlebars template string instead (see the document field).
documentanyThe email content. When type is omitted, this is opaque Chamaileon visual-editor JSON - not meant to be hand-authored. When type is "html" or "text", this is a plain string rendered with Handlebars at send time, and can use merge tags: - {{contact.email}}, {{contact.name}}, and {{contact.<yourCustomFieldName>}} for any field from GET /v1/projectId/{projectId}/contacts/fields. - {{unsubscribeLink}} and {{pauseSubscriptionLink}} - available for campaign/triggered/automation emails (subscriber-list-bound), NOT for transactional emails. - Any feed's variableName (see the feeds field) as a loop, e.g. {{#each news.item limit=5 skip=0}}...{{this.title}}...{{/each}}. - Standard {{#if}}/{{#unless}}, plus custom helpers AND, OR, NOT, EQ, INCLUDES, CAPITALIZE, TRUNCATE, DEFAULT, usable like {{#if (EQ contact.plan "pro")}}...{{/if}}. For transactional/triggered sends, whatever object is passed as data on POST /v1/projectId/{projectId}/send-transactional or /v1/projectId/{projectId}/send-triggered is merged directly into the TOP LEVEL of the template context (not nested under a "data" key) - so sending data: { "orderId": 123 } makes {{orderId}} available, not {{data.orderId}}.
feedsarray of objectRSS/Atom/JSON feeds to pull into this email - see variableName on each feed for how to reference it from document.
feeds[].urlstringyes
feeds[].feedTypestring (rss-xml | json)yes"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringyesNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.

Responses

StatusDescription
201Created
400Invalid request body or query - every violation found (missing/invalid/unexpected fields) is reported in one response, semicolon-separated, not just the first one hit.
403Missing or invalid API key

Response body

FieldTypeRequiredDescription
_idstring
namestring
subjectstring
previewTextstring
subscriberListIdstring
senderIdentitystring
replyTostring
typestring (chamaileon | html | text)
excludeUnengagedboolean
feedsarray of object
feeds[].urlstringyes
feeds[].feedTypestring (rss-xml | json)yes"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringyesNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.
createdAtstring
updatedAtstring

Get a single triggered email

GET /v1/projectId/{projectId}/triggered-emails/{id}

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
idpathstringyes

Responses

StatusDescription
200OK
403Missing or invalid API key
404Triggered Emails not found

Response body

FieldTypeRequiredDescription
_idstring
namestring
subjectstring
previewTextstring
subscriberListIdstring
senderIdentitystring
replyTostring
typestring (chamaileon | html | text)
excludeUnengagedboolean
feedsarray of object
feeds[].urlstringyes
feeds[].feedTypestring (rss-xml | json)yes"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringyesNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.
createdAtstring
updatedAtstring

Partially update a triggered email

PATCH /v1/projectId/{projectId}/triggered-emails/{id}

Merges the given fields into the existing document - omitted fields are left unchanged. Nothing is required (unlike create) since every field is optional on a partial update.

subscriberListId is re-validated the same way as create.

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
idpathstringyes

Request body

FieldTypeRequiredDescription
namestring
subscriberListIdstringRequired. If the user hasn't told you which list this triggered email is for, call GET /v1/projectId/{projectId}/subscriber-lists first and ask them to choose from the real list names - don't ask them for a raw ID.
excludeUnengagedboolean
subjectstring
previewTextstringOptional, but meaningfully affects open rates - if the user hasn't given you one, ask what they'd like it to say rather than leaving it blank.
senderIdentitystringA sender identity _id. If the user hasn't told you which one to use, call GET /v1/projectId/{projectId}/sender-identities first and ask them to choose from the real email addresses returned - don't ask them for a raw ID.
replyTostring
typestring (html | text)Omit to keep using the visual (Chamaileon) editor document. Set to "html" or "text" to author document as a raw Handlebars template string instead (see the document field).
documentanyThe email content. When type is omitted, this is opaque Chamaileon visual-editor JSON - not meant to be hand-authored. When type is "html" or "text", this is a plain string rendered with Handlebars at send time, and can use merge tags: - {{contact.email}}, {{contact.name}}, and {{contact.<yourCustomFieldName>}} for any field from GET /v1/projectId/{projectId}/contacts/fields. - {{unsubscribeLink}} and {{pauseSubscriptionLink}} - available for campaign/triggered/automation emails (subscriber-list-bound), NOT for transactional emails. - Any feed's variableName (see the feeds field) as a loop, e.g. {{#each news.item limit=5 skip=0}}...{{this.title}}...{{/each}}. - Standard {{#if}}/{{#unless}}, plus custom helpers AND, OR, NOT, EQ, INCLUDES, CAPITALIZE, TRUNCATE, DEFAULT, usable like {{#if (EQ contact.plan "pro")}}...{{/if}}. For transactional/triggered sends, whatever object is passed as data on POST /v1/projectId/{projectId}/send-transactional or /v1/projectId/{projectId}/send-triggered is merged directly into the TOP LEVEL of the template context (not nested under a "data" key) - so sending data: { "orderId": 123 } makes {{orderId}} available, not {{data.orderId}}.
feedsarray of objectRSS/Atom/JSON feeds to pull into this email - see variableName on each feed for how to reference it from document.
feeds[].urlstring
feeds[].feedTypestring (rss-xml | json)"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.

Responses

StatusDescription
200OK
400Invalid request body or query - every violation found (missing/invalid/unexpected fields) is reported in one response, semicolon-separated, not just the first one hit.
403Missing or invalid API key
404Triggered Emails not found

Response body

FieldTypeRequiredDescription
_idstring
namestring
subjectstring
previewTextstring
subscriberListIdstring
senderIdentitystring
replyTostring
typestring (chamaileon | html | text)
excludeUnengagedboolean
feedsarray of object
feeds[].urlstringyes
feeds[].feedTypestring (rss-xml | json)yes"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringyesNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.
createdAtstring
updatedAtstring

Delete a triggered email

DELETE /v1/projectId/{projectId}/triggered-emails/{id}

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
idpathstringyes

Responses

StatusDescription
200Deleted
403Missing or invalid API key
404Triggered Emails not found

Response body

FieldTypeRequiredDescription
_idstring
namestring
subjectstring
previewTextstring
subscriberListIdstring
senderIdentitystring
replyTostring
typestring (chamaileon | html | text)
excludeUnengagedboolean
feedsarray of object
feeds[].urlstringyes
feeds[].feedTypestring (rss-xml | json)yes"rss-xml" covers both RSS and Atom XML feeds - which one it actually is gets auto-detected from the feed content itself, which changes the array key inside the template (see variableName below).
feeds[].variableNamestringyesNo spaces allowed. This becomes a top-level Handlebars variable in the document (see the document field on the parent email for the templating syntax). The feed's parsed items are NOT directly at {{variableName}} - they're nested under a feed-shape-dependent array key you loop over with the custom each helper: - feedType "rss-xml" that turns out to be RSS: {{#each <variableName>.item}}...{{/each}} (array key is the literal word "item", singular) - feedType "rss-xml" that turns out to be Atom: {{#each <variableName>.entry}}...{{/each}} (array key is "entry") - feedType "json": the array key is whatever field the source JSON actually uses for its list (e.g. "items", "articles") - inspect the feed's JSON to find it, it is not fixed. Inside the loop, each item's own fields are whatever the feed provides (e.g. RSS gives {{this.title}}, {{this.link}}, {{this.pubDate}}) - there is no fixed schema, it mirrors the feed's actual content one-to-one.
feeds[].maxItemsintegerInformational only - NOT enforced when the email is sent. To actually limit how many items render, pass limit (and optionally skip) as hash arguments on the each tag itself, e.g. {{#each news.item limit=5 skip=0}}.
feeds[].requiredboolean
feeds[].availableFieldsarray of stringInformational only, for the app UI - not enforced or used when rendering.
createdAtstring
updatedAtstring

Get stats for a single triggered email

GET /v1/projectId/{projectId}/triggered-emails/{id}/stats

Aggregate counts only, never a per-recipient list - response size and query cost are bounded regardless of how much activity actually happened, since every count is a single indexed query scoped to this one resource. For the actual list of recipients (who received/opened/clicked/bounced/unsubscribed), see GET /v1/projectId/{projectId}/triggered-emails/{id}/recipients.

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
idpathstringyesThe triggered email _id.
fromquerystringOnly count events at or after this date/time.
toquerystringOnly count events at or before this date/time. If both from and to are given, the range between them cannot exceed 366 days.

Responses

StatusDescription
200OK
400Invalid request body or query - every violation found (missing/invalid/unexpected fields) is reported in one response, semicolon-separated, not just the first one hit.
403Missing or invalid API key
404Triggered Emails not found

Response body

FieldTypeRequiredDescription
opensintegerTotal open events (a single recipient opening twice counts twice).
clicksintegerTotal click events.
uniqueOpensintegerNumber of distinct dispatched emails with at least one open.
uniqueClicksintegerNumber of distinct dispatched emails with at least one click.
sentinteger
failedinteger
bounceinteger
complaintinteger

List recipients of a single triggered email

GET /v1/projectId/{projectId}/triggered-emails/{id}/recipients

One row per actual recipient - who received it, how many times they opened/clicked, and whether they bounced/complained/unsubscribed/paused/subscribed/resubscribed as a result of this specific send. Supports the usual filter[<field>] exact-match filtering (e.g. filter[unsubscribed]=true, filter[status]=failed), plus two convenience filters not tied to a literal field: filter[opened]=true/false and filter[clicked]=true/false.

Parameters

NameInTypeRequiredDescription
projectIdpathstringyesThe project _id, found in the app under Project Settings.
idpathstringyesThe triggered email _id.
limitqueryintegerMax items per page. Capped at 30.
skipqueryintegerNumber of items to skip, for pagination.
sortquerystringField to sort by.
orderquerystring
filter[&lt;field&gt;]querystringExact-match filter on a top-level field, e.g. filter[name]=Welcome. Repeatable for multiple fields.

Responses

StatusDescription
200OK
400Invalid request body or query - every violation found (missing/invalid/unexpected fields) is reported in one response, semicolon-separated, not just the first one hit.
403Missing or invalid API key
404Triggered Emails not found

Response body

FieldTypeRequiredDescription
itemsarray
countintegerTotal matching items across all pages, not just this page.
nextstringFull URL (path + query string) of the next page, or null if this is the last page.