Skip to main content

Emailing

Pre-requisites​

Required when creating​

  • At least one Folder created to organize your campaigns.
  • At least one Email created to be used in the campaign.

Required before sending​

  • At least one List created containing the Contacts you want to send the email to.

Steps to create an EmailCampaign​

1 - Create a Folder resource (if needed)​

If you do not already have a Folder, you must create one first by making a POST request to the /v1/folder/ endpoint.

POST /v1/folder/ HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
Content-Type: application/json
{
"name": "<folder_name>",
"description": "<folder_description>"
}
response
{
"_id": "<folder_id>", //<--- use this ID in the next steps
"name": "<folder_name>",
"description": "<folder_description>"
// ...
}

2 - Create your Email resource​

Before creating an EmailCampaign, you must create at least one Email resource containing the email design by making a POST request to the /v1/email/ endpoint.

POST /v1/email/ HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
Content-Type: application/json
{
"name": "Name of the email",
"description": "Description of the email",

"subject": "Subject of the email",
"preheader": "Preheader text that will appear after the subject",
"fromemail": "[email protected]",
"fromname": "Name of the sender",
"unsubscribetext": "Click here to unsubscribe",

"editor": "bee",
"body": "<bee_html>",
"json": "<bee_json>"
}
response
{
"_id": "<email_id>", //<--- use this ID in the next steps
"name": "Name of the email",
// ...
}

HTML & JSON​

We recommend using our visual editor to create the design for the Email resource, it will generate both the HTML body & json attributes. That said, static content is rarely what people want when using the API. To create a dynamic email design we recommend the following options :

Simple Text Replacements
For simple use cases, use our visual editor to create a base design and use it as a template for your dynamic content. Add easily identifiable placeholders like {{my_custom_text}} in the content. We already use the "mustaches" {{...}} syntax as merge tags for contact properties during rendering and strip out any unknown tags before sending. This can be useful for simple text replacements.

Example, you create an EmailCampaign & Email via the UI with Hello {{firstname}}, here is your special offer: {{special_offer_code}} in a text block. You will use this Email as a template. Fetch it and only replace the {{special_offer_code}} tag with your own promo code in both the json & body which you will use to create a new EmailCampaign & Email resources via the API. You kept the {{firstname}} tag as is, it will be replaced when sending with the actual contact's first name by our rendering engine as expected.

This approach is simple but limited to text replacements only, if you need more complex dynamic content with HTML you should consider the next option.

HTML Block
Same approach as above but instead of a text block, use an HTML block. Add a placeholder tag like {{dynamic_content}}. In your code, fetch the Email resource, replace the {{dynamic_content}} tag in both the json &body with your own generated HTML content. You can generate complex HTML content with your own code/templates.

warning

Be careful to generate valid HTML for mail clients which isn't the same as regular web HTML.

3 - Create your EmailCampaign resource​

Finally, you can create your EmailCampaign by making a POST request to the /v1/campaign/ endpoint using the Folder ID from step 1 and the Email ID(s) from step 2.

POST /v1/campaign/ HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
Content-Type: application/json
{
"name": "Name of the email campaign",
"description": "Description of the email campaign",

"type": 2, /* EMAIL */
"status": "d", /* DRAFT */
"folder": "<folder_id>",
"start": "2026-01-01T00:00:00.000Z",
"batchConfiguration": { "type": "none" },

/* Email variants (A/B testing) */
"emails": [
{ "email": "<email_id>", "weight": 100 }
],

/* Lists of contacts to send to */
"lists": [
{ "list": "<list_id>" }
],

/* Mailing List (if GDPR feature is enabled) */
"mailinglist": {
"mailinglist": "<mailinglist_id>",
"statusFilter": 0, /* OPT_IN_ONLY: 0, UNDEFINED_ONLY: 1, OPT_IN_OR_UNDEFINED: 2 */
"language": null, /* "fr", "en" */
},
}
response
{
"_id": "<campaign_id>",
"name": "Name of the email campaign",
// ...
}
warning

Our system always assumes that an Email belongs to a single EmailCampaign.

Do not share an Email entity between multiple EmailCampaigns, always create separate entities. If you want to use the same design, simply duplicate the Email entity first. The simplest way to do this is to make a GET request to retrieve the existing Email entity, extract the json & body attributes, then make a POST request to create a new one with the extracted data.

Special "All Contacts" List​

By setting the list attribute to null, you will use the special "All Contacts" to send the EmailCampaign to all Contacts of the Client :

PUT /v1/campaign/<campaign_id> HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
Content-Type: application/json

{ "lists": [{ "list": null }] }
info

By design, we made it explicit to avoid sending accidentally to all Contacts if no List is specified (campaign.lists is empty).

Excluding Lists​

You can exclude specific Lists from your campaign by setting excluded: true. This is useful when you want to target a broad audience but exclude certain segments.

Example: Include one list, exclude another

{
"lists": [
{ "list": "<include_list_id>" },
{ "list": "<exclude_list_id>", "excluded": true }
]
}

Example: Send to all contacts except a specific list

{
"lists": [
{ "list": null },
{ "list": "<exclude_list_id>", "excluded": true }
]
}

Sending your EmailCampaign​

Schedule​

Setting the start attribute of the EmailCampaign allows you to schedule when the sending should start. To send immediately use the current (or past) datetime.

Flow Control​

Setting the batchConfiguration attribute of the EmailCampaign allows you to configure the flow control.

No batching, send as fast as possible all at once.

{ "batchConfiguration": { "type": "none" } }

Send​

When you are ready, simply update the EmailCampaign's status to a (ACTIVE).

PUT /v1/campaign/<campaign_id> HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
Content-Type: application/json
{
"status": "a",
"start": "2024-06-01T12:00:00.000Z",
"batchConfiguration": { "type": "none" }
}

Retrieving EmailCampaign Metrics​

After sending an EmailCampaign, you can retrieve performance metrics to analyze your campaign's effectiveness.

Metric API Endpoint​

GET /v1/metric/<element_type>/<element_id>/email

Path Parameters:

  • element_type:
    • campaign - Metrics for an EmailCampaign (all variants)
    • target - Metrics for a specific Email variant
    • owner - Metrics across all of the client's EmailCampaigns
  • element_id: The ID of the element (Campaign, Email, or Client for owner)

Query Parameters:

  • compile=true: Get aggregated totals across all time
  • startdate + enddate: Get metrics for a date range (ISO 8601 format), grouped by day by default
  • groupBy: day (default), week, or month — only applies with startdate + enddate. When set, the startdate/enddate range is capped at 12 months (400 if exceeded); this cap does not apply when groupBy is omitted.
  • breakdown: campaign, automation, folder, or target — only valid with element_type=owner. Returns a per-item breakdown alongside the aggregate total, instead of a single aggregate. A fifth value, source, exists on the session subject only (traffic source breakdown, with sub-sources nested as children) and does not apply to email metrics. The response also carries summary: the whole-range owner totals (with rates), so you don't need to sum buckets or make a second call.
  • timezone: IANA timezone name (e.g. America/Toronto). Reads startdate/enddate as that zone's local day and buckets groupBy in that zone. Defaults to UTC.

Metric Definitions:

MetricDescription
sentTotal number of emails successfully sent
openTotal number of email opens (includes multiple opens by same contact)
uniqueopenNumber of unique contacts who opened the email (counted once per contact)
clickTotal number of link clicks (includes multiple clicks by same contact)
uniqueclickNumber of unique contacts who clicked a link (counted once per contact)
bounceNumber of emails that bounced (hard and soft bounces)
unsubscribeNumber of contacts who unsubscribed
spamNumber of spam complaints
openRateuniqueopen / sent * 100, rounded to 2 decimals, 0 when sent is 0
clickRateuniqueclick / sent * 100, rounded to 2 decimals, 0 when sent is 0
unsubscribeRateunsubscribe / sent * 100, rounded to 2 decimals, 0 when sent is 0

Rate fields appear on the compile=true row, every groupBy bucket, and every breakdown item/summary — always, whether or not groupBy is used there.

Response Schemas​

Total Metrics (compile=true, optional startdate/enddate)​

[
{
"_id": <element_id>,
"sent": number,
"open": number,
"uniqueopen": number,
"click": number,
"uniqueclick": number,
"bounce": number,
"unsubscribe": number,
"spam": number,
"openRate": number,
"clickRate": number,
"unsubscribeRate": number
}
]

Daily Metrics (startdate/enddate, groupBy=day)​

[
{
"_id": { "group": <element_id>, "year": number, "month": number, "day": number },
"date": string, // this bucket's start, ISO 8601 UTC (local midnight in `timezone`, else UTC)
"sent": number,
"open": number,
"uniqueopen": number,
"click": number,
"uniqueclick": number,
"bounce": number,
"unsubscribe": number,
"spam": number,
"openRate": number,
"clickRate": number,
"unsubscribeRate": number
}
]

Weekly Metrics (startdate/enddate, groupBy=week)​

[
{
"_id": { "group": <element_id>, "isoYear": number, "isoWeek": number },
"date": string, // this bucket's start: local midnight of the ISO week's Monday
"sent": number,
// ...same metrics and rates as above
}
]

Monthly Metrics (startdate/enddate, groupBy=month)​

[
{
"_id": { "group": <element_id>, "year": number, "month": number },
"date": string, // this bucket's start: local midnight of the 1st
"sent": number,
// ...same metrics and rates as above
}
]

When groupBy is passed, the response is zero-filled: exactly one row per bucket from startdate to enddate inclusive, ascending, with every metric at 0 for buckets with no activity. Omitting groupBy keeps the older behavior (day buckets, but only for days that actually have data, no date field, no rate fields) for existing callers.

Breakdown Metrics (element_type=owner, breakdown=campaign|automation|folder|target)​

{
"total": [ /* same shape as above, for the requested groupBy */ ],
"items": [
{
"id": <campaign_or_automation_or_folder_or_email_id>,
"name": string | null, // resolved from the element's own collection; null for breakdown=target or a missing document
"type": number | null, // campaign type, breakdown=campaign only
"start": Date | null, // campaign send date, when set
"created": Date | null,
"deleted": boolean, // soft-deleted elements keep their metrics and are flagged here
"totals": {
"sent": number,
"open": number,
"uniqueopen": number,
"click": number,
"uniqueclick": number,
"bounce": number,
"unsubscribe": number,
"spam": number,
"openRate": number,
"clickRate": number,
"unsubscribeRate": number
}
}
],
"summary": {
// same shape as one item's `totals` - the whole-range owner totals, with rates
}
}

items only lists elements with at least one non-zero metric in the range, ordered by sent descending. There is no limit on how many items come back — a client with 2,000 campaigns gets 2,000 items. Each item carries its display fields (name, type, start, created, deleted) resolved in the same aggregation, so there is no need to list campaigns or automations separately to label them.

Get Total Metrics for a Campaign​

To retrieve the total aggregated metrics for an EmailCampaign, use the compile=true parameter:

GET /v1/metric/campaign/<campaign_id>/email?compile=true HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
response
{
"_id": "<campaign_id>",
"sent": 1000,
"open": 250,
// ...
}
info

This returns metrics aggregated across all Email variants in the EmailCampaign.

Use target instead of campaign in the path to retrieve metrics for a specific Email variant:
GET /v1/metric/target/<email_id>/email?compile=true

Get Daily Metrics for a Campaign​

To retrieve metrics broken down by day for an EmailCampaign, zero-filled for the full range:

GET /v1/metric/campaign/<campaign_id>/email?startdate=2025-01-01&enddate=2025-01-31&groupBy=day HTTP/1.1
Host: rest.leadfox.co
Authorization: JWT <jwt>
response
[
{
"_id": { "group": "<campaign_id>", "year": 2025, "month": 1, "day": 1 },
"date": "2025-01-01T00:00:00.000Z",
"sent": 100,
"open": 25,
// ...
},
{
"_id": { "group": "<campaign_id>", "year": 2025, "month": 1, "day": 2 },
"date": "2025-01-02T00:00:00.000Z",
"sent": 0,
"open": 0,
// no activity that day, every metric is 0 - the row is still present
}
// ...one row per day through 2025-01-31
]
info

Use target instead of campaign in the path to retrieve daily metrics for a specific Email variant:
GET /v1/metric/target/<email_id>/email?...

warning

Omitting groupBy keeps the older behavior for existing callers: day buckets, but only for days that have data (no zero-fill, no date field). Pass groupBy=day (or week/month) to get the full, zero-filled range described above.