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. Contact data is ONLY available under the contact. prefix - bare tags like {{name}}, {{firstName}} or {{email}} render as empty strings. For a fallback use DEFAULT, e.g. Hi {{DEFAULT contact.name "there"}},. - {{unsubscribeLink}} and {{pauseSubscriptionLink}} - available for campaign/triggered/automation emails (subscriber-list-bound), NOT for transactional emails. - {{webVersionLink}} - campaigns only: a public "view in browser" link to the campaign (the same link for every recipient, also shown on the campaign card in the app). It is not personalized - contact merge tags show their DEFAULT fallback. It only works while the campaign's webVersionEnabled is true and its status is "sent" or "archive" (when adding this tag, also set webVersionEnabled: true), and always shows the latest version of the campaign (edits made after sending are visible to earlier recipients). - 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.
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).

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
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).
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
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).
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. Contact data is ONLY available under the contact. prefix - bare tags like {{name}}, {{firstName}} or {{email}} render as empty strings. For a fallback use DEFAULT, e.g. Hi {{DEFAULT contact.name "there"}},. - {{unsubscribeLink}} and {{pauseSubscriptionLink}} - available for campaign/triggered/automation emails (subscriber-list-bound), NOT for transactional emails. - {{webVersionLink}} - campaigns only: a public "view in browser" link to the campaign (the same link for every recipient, also shown on the campaign card in the app). It is not personalized - contact merge tags show their DEFAULT fallback. It only works while the campaign's webVersionEnabled is true and its status is "sent" or "archive" (when adding this tag, also set webVersionEnabled: true), and always shows the latest version of the campaign (edits made after sending are visible to earlier recipients). - 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.
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).

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
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).
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
trackOpensbooleanDefaults to true. When false, no open-tracking pixel is added to this email, so opens are not recorded (open stats and open-based segments/conditions get no data from it).
trackClicksbooleanDefaults to true. When false, links in this email are sent as-is instead of through the click tracker, so clicks are not recorded (click stats and click-based segments/conditions get no data from it).
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. Pass interval + metric for an hourly/daily/weekly/monthly breakdown instead of totals.

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.
intervalquerystringReturns an EmailStatsTimeline (per-period breakdown, same as the app's stats chart) instead of totals. Requires metric. With interval, from/to are rounded to whole UTC days and default to the last 7 days (hourly) or the last month (others). Max range: hourly 7 days, daily 60 days, weekly/monthly 12 months (405 when exceeded).
metricquerystringWith interval: what to count per period. subscribe/unsubscribe/resubscribe/pause-subscription/unpause-subscription count subscription changes caused by this email.
timeZonequerystringWith interval: IANA time zone the periods are labelled in. Defaults to UTC.

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
405The operation is blocked by a business rule (e.g. the resource is still in use)

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.