Skip to main content

Email Campaigns

SendSeven uses a 3-tier architecture for email campaigns that separates design from content. This guide walks you through the full workflow: choosing a template, creating content, and launching a campaign.

Required Scopes

ScopePurpose
campaigns:readView templates, content, campaigns, and analytics
campaigns:createCreate templates, content, and campaigns
campaigns:updateEdit campaign content/settings and schedule
campaigns:sendSend, pause, resume, and cancel campaigns

Architecture

EmailTemplate (MJML template with slots)
|
EmailContent (fills the slots with actual content)
|
EmailCampaign (campaign instance with recipients and schedule)

This approach lets you create reusable templates and populate them with different content for each campaign.

Pricing

Every plan is a fixed monthly base fee that includes a pool of usage shared across messages, campaign messages, KB queries, and campaign emails. Emails beyond that pool are billed per email:

PlanBase feeIncluded pool / monthPer email beyond the pool
BasicEUR 492,500EUR 0.010
ProfessionalEUR 792,500EUR 0.010
ScaleEUR 19910,000EUR 0.0075
EnterpriseEUR 49940,000EUR 0.005
API OnlyEUR 9 per connected channel1,000 per connected channelEUR 0.0025

Managed (SendSeven handles delivery) and BYOK (you connect your own SendGrid, Mailgun, or AWS SES account) cost the same per email — BYOK is about owning your sending reputation and provider relationship, not a lower rate.

The included pool is applied to your most expensive usage first, so emails — the cheapest unit — are the last to draw from it. See the pricing page for current rates.


Step 1: Email Templates

Email templates define the visual structure using MJML (a responsive email markup language). They contain named slots that are filled by email content.

List Templates

curl -X GET "https://api.sendseven.com/api/v1/email-templates" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"items": [
{
"id": "layout_newsletter_01",
"name": "Standard Newsletter",
"description": "Single-column newsletter with header image, body, and footer",
"slots": ["header_image", "headline", "body", "cta_text", "cta_url"],
"created_at": "2026-01-05T10:00:00Z"
}
]
}

Create a Template

curl -X POST "https://api.sendseven.com/api/v1/email-templates" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Simple Announcement",
"description": "Clean single-column announcement layout",
"mjml": "<mjml><mj-body><mj-section><mj-column><mj-image src=\"{{header_image}}\" /><mj-text>{{headline}}</mj-text><mj-text>{{body}}</mj-text><mj-button href=\"{{cta_url}}\">{{cta_text}}</mj-button></mj-column></mj-section></mj-body></mjml>"
}'

Step 2: Email Content

Email content fills the template's slots with actual text, images, and links.

Create Content

curl -X POST "https://api.sendseven.com/api/v1/email-contents" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"layout_id": "layout_newsletter_01",
"name": "March Newsletter Content",
"subject": "March highlights from Acme",
"sender_name": "Acme Team",
"sender_email": "[email protected]",
"slot_data": {
"header_image": "https://cdn.acme.com/images/march-header.jpg",
"headline": "March Highlights",
"body": "<p>Spring is here and so are new features! Check out what we have launched.</p>",
"cta_text": "See What Is New",
"cta_url": "https://acme.com/blog/march-2026"
}
}'
FieldTypeRequiredDescription
layout_idstringYesEmail template to use
namestringYesInternal name
subjectstringYesEmail subject line
sender_namestringYesSender display name
sender_emailstringYesSender email address
slot_dataobjectYesKey-value pairs matching the template's slots
tip

Always use email_content_id when creating campaigns to reference your email content.

Searching email content

GET /api/v1/email-contents accepts a search query parameter that matches on content name and subject. When search is provided, pagination is skipped and the endpoint returns up to 50 matches across all pages in a single response — this keeps the Send-Message node's email-content picker snappy without forcing the caller to paginate.

curl -X GET "https://api.sendseven.com/api/v1/email-contents?search=march" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00"

For normal browse-style listing (no search parameter), the endpoint paginates as usual using page / page_size.


Step 3: Create and Send the Campaign

Create an Email Campaign

curl -X POST "https://api.sendseven.com/api/v1/email-campaigns" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "March Newsletter",
"email_content_id": "content_mar_newsletter",
"list_ids": ["list_newsletter"],
"scheduled_at": "2026-03-01T08:00:00Z"
}'
FieldTypeRequiredDescription
namestringYesCampaign name
email_content_idstringYesEmail content to send
list_idsarrayYesContact list IDs
scheduled_atstringNoISO 8601 datetime for scheduled delivery

Send Immediately

curl -X POST "https://api.sendseven.com/api/v1/email-campaigns/ecamp_march_01/send" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"

Get Analytics

curl -X GET "https://api.sendseven.com/api/v1/email-campaigns/ecamp_march_01/analytics" \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json"
{
"campaign_id": "ecamp_march_01",
"sent": 12500,
"delivered": 12210,
"opened": 4884,
"clicked": 1587,
"bounced": 290,
"complained": 5,
"unsubscribed": 23,
"delivery_rate": 97.68,
"open_rate": 40.00,
"click_rate": 13.00,
"bounce_rate": 2.32,
"complaint_rate": 0.04
}

Complete Workflow (Python)

import requests

BASE_URL = "https://api.sendseven.com/api/v1"
HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
}

# Step 1: Choose an email template
templates = requests.get(f"{BASE_URL}/email-templates", headers=HEADERS).json()
layout_id = templates["items"][0]["id"]
print(f"Using template: {layout_id}")
print(f"Available slots: {templates['items'][0]['slots']}")

# Step 2: Create email content
content = requests.post(
f"{BASE_URL}/email-contents",
headers=HEADERS,
json={
"layout_id": layout_id,
"name": "April Promo Content",
"subject": "Spring savings - 25% off this week only!",
"sender_name": "Acme Store",
"sender_email": "[email protected]",
"slot_data": {
"header_image": "https://cdn.acme.com/spring-sale.jpg",
"headline": "Spring Savings Event",
"body": "<p>Save 25% on all orders this week. Use code SPRING25.</p>",
"cta_text": "Shop Now",
"cta_url": "https://shop.acme.com/spring-sale",
},
},
).json()
print(f"Created content: {content['id']}")

# Step 3: Create and schedule the campaign
campaign = requests.post(
f"{BASE_URL}/email-campaigns",
headers=HEADERS,
json={
"name": "Spring Sale Email",
"email_content_id": content["id"],
"list_ids": ["list_newsletter", "list_active_customers"],
"scheduled_at": "2026-04-01T08:00:00Z",
},
).json()
print(f"Campaign scheduled: {campaign['id']} for {campaign['scheduled_at']}")
print(f"Recipients: {campaign['recipient_count']}")

JavaScript Example

const BASE_URL = "https://api.sendseven.com/api/v1";
const HEADERS = {
"Authorization": "Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00",
"Content-Type": "application/json",
};

// Create email content
const contentRes = await fetch(`${BASE_URL}/email-contents`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
layout_id: "layout_newsletter_01",
name: "Product Update Email",
subject: "New feature: Automated Workflows",
sender_name: "Acme Product Team",
sender_email: "[email protected]",
slot_data: {
header_image: "https://cdn.acme.com/workflows.jpg",
headline: "Introducing Automated Workflows",
body: "<p>Set up triggers, conditions, and actions to automate your messaging.</p>",
cta_text: "Learn More",
cta_url: "https://acme.com/blog/automated-workflows",
},
}),
});
const content = await contentRes.json();

// Create and send the campaign
const campaignRes = await fetch(`${BASE_URL}/email-campaigns`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
name: "Workflows Announcement",
email_content_id: content.id,
list_ids: ["list_newsletter"],
}),
});
const campaign = await campaignRes.json();

// Send immediately
await fetch(`${BASE_URL}/email-campaigns/${campaign.id}/send`, {
method: "POST",
headers: HEADERS,
});
console.log("Campaign is sending!");

Error Responses

StatusError CodeDescription
401INVALID_TOKENToken is invalid or expired
403INSUFFICIENT_SCOPEToken lacks required campaigns scope
404RESOURCE_NOT_FOUNDTemplate, content, or campaign not found
409CONFLICTCannot modify a campaign that is already sending
422VALIDATION_ERRORslot_data does not match template slots

Next Steps