Write, test and send email campaigns
Write a campaign with the block designer, check it, send yourself a test, schedule it, and read its opens, clicks, bounces, orders and revenue.
In the dashboard
Campaigns is where you write the emails you send once to your audience. A campaign is one email, sent to lists and segments, now or at a time you choose. You write it with a block designer, check it, send yourself a test, and schedule it. Afterwards its report shows who opened and clicked, what bounced, and the orders and revenue it brought.
Before you begin
Section titled Before you begin- Set up the sending domain, its DNS records and your postal address (Set up your sending domain and warm-up). You can write campaigns before that, but none can start.
- You need Tier 1 support or higher to read campaigns and their reports. You need Tier 3 support or higher to write one and send a test. Scheduling, sending, pausing, resuming, cancelling and resending need an owner or admin.
- Have an audience: a list or a segment with subscribed contacts (Manage your email contacts, lists and segments).
- For a coupon block, create the promotion first (Create promotions and issue gift cards). For a product grid, publish the products.
Start a campaign
Section titled Start a campaign- In the dashboard, open the organization, then Commerce, then Email, then Campaigns.
- Select New campaign….
- Enter a Name. Only you see it. You write the subject next.
- Under Start from, choose A blank design, one of the Starter designs, or one of your Saved designs.
- Select Create campaign.
The starter designs are plain first drafts to edit: Newsletter, Product announcement, Sale with coupon, Welcome, Restock reminder, Plain letter, Order follow-up thank you and Back in stock. Product grids and coupons start empty, so you choose your own products and promotion.
The editor opens with two views, Setup and Design, under What to edit. Save keeps everything. Campaign actions has Open the report, Duplicate campaign and Delete campaign…. The list of campaigns has the status filters All, Draft, Scheduled, Sending, Paused, Sent and Cancelled, and Search by name.
Set up the campaign
Section titled Set up the campaignSetup has these cards:
- Content
- The Campaign name, the Subject and the Preview text, the line many inboxes show after the subject. Merge tags work in both. The subject shows its length, and many inboxes cut a subject at 78 characters.
- Sender
- The Sender name, the Address before the @ and Reply to. Leave them empty to use the ones in your sending settings. The address ends with your sending domain.
- Audience
- Who gets the campaign. Tick Send to these lists and Send to these segments. A contact gets it when it is on any ticked list or in any ticked segment. Under Leave some contacts out, tick Not on these lists and Not in these segments to remove people. With nothing ticked, every subscribed contact gets it. The estimate above says how many contacts will get the campaign, after a short pause. Only subscribed contacts that are not on the suppression list are counted or mailed.
- A/B test
- See Test two subjects against each other.
- Tracking
- Count opens adds a one-pixel image to each email. Count clicks sends links through our click tracker. UTM campaign and UTM content set the values added to your store's links.
- Sending speed
- Emails each minute is the most this campaign sends in a minute. Leave it empty to use the limit in your sending settings. Your daily limit during warm-up still applies.
Links to your store in the email gain utm_source=coritan, utm_medium=email, utm_campaign (your UTM campaign, or a short form of the campaign's name) and utm_content (your UTM content, or the version's letter). A value already on the link stays as written. A link to your store also gains mkt, a code for the one reader, which lets the storefront credit an order to this email.
A campaign changes only while it is a draft or scheduled. Saving a scheduled campaign returns it to a draft, so schedule it again. A campaign that has started cannot change. Duplicate it to send a changed version.
Test two subjects against each other
Section titled Test two subjects against each otherAn A/B test sends two or three versions to a small, random part of your audience. After the wait you set, the version with the best result goes to everyone else.
- On Setup, turn on Run an A/B test. Version A is the subject, preview text and sender above.
- Fill in version B: its Subject, Preview text and Sender name. Select Add version C for a third. The remove button on the last version drops it.
- Set Share of the audience in the test, from 10 to 50 percent, split evenly between the versions.
- Under The winner has, choose Most opens, Most clicks or Most revenue.
- Set Send the winner after, from 1 to 72 hours.
The rest of the audience gets nothing until the winner is picked. The result is compared per email sent, and opens by mail privacy tools are left out. Most revenue falls back to clicks while no order is credited to any version. The report's A/B test card lists each version and marks the Winner. A test needs at least a version B, or the campaign refuses to schedule.
Design the email
Section titled Design the emailOpen the Design view. The designer has the block list on the left, the preview in the middle and the properties of the chosen block on the right. Under 1024 pixels wide they stack.
- Add block adds a block. Select a block in the list to edit it. Use the arrows to move it, the copy button to duplicate it and the bin to delete it. You can also drag a block.
- Undo and Redo step through your changes.
- Start from… replaces the design with a starter design or a saved design. You can undo it.
- Save design… keeps the design, with its subject and preview text, as a saved design. Campaigns made from it keep their own copy.
- The preview follows a moment after you stop typing. Choose Desktop or Phone, and Light or Dark. Issues the preview finds list under it, and each one with a block selects that block.
A design holds up to 80 blocks, and columns hold up to 10 blocks each. These are the blocks:
| Block | What it holds |
|---|---|
| Heading | Text of up to 300 characters, a size (Large, Medium or Small), alignment and colour. |
| Text | Rich text with paragraphs, line breaks, bold, italic, underline, links and lists. |
| Image | An https Image address, Alt text, an optional Link and a Width from 16 to 1200 pixels. |
| Button | A Label of up to 60 characters, a Link and a colour. It defaults to the design's accent colour. |
| Divider | A line across the email. |
| Spacer | A Height from 4 to 160 pixels. |
| Columns | Two or three columns, each holding other blocks except columns and the footer. Columns do not nest. |
| Social links | Up to eight links to Facebook, Instagram, X, TikTok, YouTube, LinkedIn, Pinterest, Threads, Bluesky, Reddit, Discord or a website. |
| HTML | Your own HTML. Scripts, event handlers and anything that embeds are removed when you save. Use tables and inline styles. |
| Product grid | Up to 12 products from your catalogue, in one, two or three columns, with an option to Show the price and a Button label. The price and the link come from the catalogue when the email is sent, and a product that is not published is left out. |
| Coupon | A Promotion, a Label such as Your code, an optional Note and Valid for in hours, from 1 to 8,760. Each reader gets a single-use code copied from the promotion when the email is sent. The preview and test sends show SAMPLE-CODE. |
| Footer | Optional Text, Show the postal address and Show the unsubscribe link. One footer closes every email. |
| Disclaimer | A fixed paragraph in your own words, up to 1,500 characters. |
Design settings shows when no block is chosen: Width (480 to 720 pixels), Font, Header (Store name or None), and the Accent colour, Page colour, Message colour, Text colour and Link colour. A designed email is table-based HTML that fits phones and has a dark mode.
Every email that leaves carries the footer with your postal address and the unsubscribe link, whatever the footer shows in the preview. A footer that hides either fails the checks below.
Merge tags
Section titled Merge tagsA merge tag is a placeholder such as {{ first_name }} that the email fills in with each reader's own details when it is sent. Select Insert merge tag to add one. Choose the Detail and, where a fallback makes sense, a Fallback for readers who have none. The email then holds {{ first_name|default("there") }}.
| Tag | What it holds |
|---|---|
{{ first_name }}, {{ last_name }}, {{ email }} |
The contact's details. |
{{ fields.key }} |
A custom field, by its key. |
{{ store_name }} |
Your store's name. |
{{ shop_url }} |
Your storefront's address. |
{{ unsubscribe_url }}, {{ preferences_url }} |
The links that unsubscribe the reader and open their preference center. |
{{ coupon_code }} |
The reader's code, inside a coupon block. |
A tag that names nothing renders empty, or as its fallback. It never shows an error or the word None. A tag that cannot be read refuses the save and names the block.
Check before sending
Section titled Check before sendingSelect Check before sending…. The dialog opens as Ready to send or Not ready to send, shows the Recipients and the spam score, and groups the issues. The spam score runs from 0 (clean) to 10 and comes from the subject and the words of the email. Mail filters make their own call, so a low score is no promise. The checks run again each time you open the dialog, and nothing is sent or saved.
- To fix before sending holds errors. A campaign with an error cannot be scheduled.
- To check holds warnings. They advise and never stop a send.
- Tips holds ideas.
An issue that names a block has Show block. This is what the checks look for:
| Level | Check |
|---|---|
| Error | No subject (missing_subject), no content (no_content), no recipients (no_recipients), the sending domain not set up (sender_not_set), no postal address (no_address, also when the footer hides it), no unsubscribe link (no_unsubscribe), and a design the schema refuses (invalid_design). |
| Error | A block that links nowhere (missing_link), a link that is not a valid web, email or phone address (malformed_link), a link to a local address (local_link), a coupon with no promotion or one made from a single-use code (coupon_unavailable), and sending that is not ready (sending_not_ready). |
| Warning | A link that starts with http (insecure_link), an image with no alt text (image_missing_alt), an empty text block, button or image, an empty product grid, and products that are not published. |
| Warning | A message over 102 KB, which Gmail clips and hides the footer of (message_too_large), a subject over 78 characters, a subject in capital letters, words spam filters watch for (spam_words), and a spam score of 6 or more (spam_risk). |
| Warning | Today's daily limit is used up (daily_limit). A send that does not fit continues the next day. |
| Tip | A transparent PNG that can vanish on a dark background (dark_mode_image), and an email with no button (no_call_to_action). |
An A/B test is checked version by version, and each issue starts with the version.
Send a test
Section titled Send a testSelect Send test…. Enter up to five addresses in Send to, separated by commas or lines. For an A/B test, choose the Version. Then select Send test.
The subject starts with [Test], the merge tags use a sample contact, links are not tracked and a coupon shows a sample code. An address on the suppression list is skipped, and the dialog says which. A person or key can send 20 test requests an hour. A test needs the sending domain set up, as a real send does.
Schedule or send
Section titled Schedule or sendSelect Schedule…. The dialog shows the Campaign, Subject, Sender, Audience and Recipients.
- Choose Send now, which queues every recipient at once and sends as fast as your limits allow, or Send at a time.
- For Send at a time, pick the Date and time. The dialog names the time zone: your organization's, or your browser's when the organization has none set. The time must be in the future.
- Select Send now or Schedule campaign.
- Confirm it is you. The dashboard asks for your password, or a code when two-factor authentication is on (Confirm it is you). An API key with the send scope is never asked.
The campaign becomes Sending or Scheduled, and the dashboard opens its report. A scheduled campaign is queued when its time comes. An audience of more than 20,000 contacts is queued within a minute instead of at once.
If sending is not ready, the dialog says Sending is not ready, with the reason and a link to Sending. If the checks find an error, it says The checks found a problem and lists the errors.
Pause, resume and cancel
Section titled Pause, resume and cancelAn owner or admin can change a campaign that has started, from its report or from Campaigns:
- Pause… stops a sending campaign. Emails not sent yet wait and go out when you resume.
- Resume continues a paused campaign. It needs sending to be ready.
- Cancel campaign… ends a scheduled, sending or paused campaign for good. Emails not sent yet are skipped. Emails already sent stay in the figures.
A campaign also pauses by itself when, after at least 200 emails are delivered, its bounce rate or spam report rate goes over your limits, or when its scheduled time comes and sending is not ready. The report says why, and you resume it after you fix the cause (Set up your sending domain and warm-up).
Read the report
Section titled Read the reportA campaign that has started opens on its report. A campaign that is sending updates every ten seconds, with the number sent out of the recipients. Rates are shares of the emails delivered, or of those sent until delivery is known.
- Results
- Delivered (out of the emails sent), Open rate, Click rate, Unsubscribed, Bounced, Marked as spam, Orders and Revenue. The open and click rates also show how many readers opened and clicked.
- Opens and clicks over time
- A chart By hour or By day.
- Link clicks
- Each link in the email with its clicks, readers and share of all clicks.
- Devices, Mail apps and Countries
- Where opens and clicks came from.
- A/B test
- Each version's emails sent, opens and clicks with their rates, and revenue.
- Bounces
- Why emails did not arrive, with the number for each reason.
- Recipients
- Everyone the campaign was queued for, with the Version, the Result, Opens, Clicks, Revenue and when it was Sent. Search by address, and filter the Result: Any result, Delivered, Opened, Clicked, Unsubscribed, Bounced, Marked as spam, Failed, Delayed, Waiting to go or Held back.
Machine opens
Section titled Machine opensSome mail apps, such as Apple Mail's privacy protection, and some security scanners open every message and load every image as it arrives. Those opens say nothing about the reader. We count them apart as machine opens, and leave them out of open rates, clicks, a contact's engagement and the A/B test. The report shows how many there were. A click by a link scanner is left out the same way. Without this split, a campaign's open rate would read far higher than the number of people who read it.
An open or click counts as machine made when it comes from a mail privacy proxy or a scanner, has no browser information, or arrives within seconds of the send.
How orders and revenue are credited
Section titled How orders and revenue are creditedAn order is credited to one email, and only once. We use the first rule that finds an email:
- The code your storefront kept. A link to your store carries
mkt, the code for that reader's email. When the storefront keeps it with the shopper's visit, the order goes to that email (Send where the shopper came from). The last visit is read first. - The reader's email address. Otherwise the order goes to the email sent to the order's address that the reader last clicked, as a person and not a scanner.
Either way, the click must fall in the 7 days before the order was placed. A cancelled or refunded order is never credited. Orders counts the credited orders, and Revenue adds up their totals as they are. A store that sells in several currencies sees them added together without conversion.
Resend to people who did not open
Section titled Resend to people who did not openOnce a campaign is Sent, an owner or admin can open Campaign actions on its report and choose Resend to non-openers…. Enter what to Start the subject with, such as the default Reminder:, or leave it empty to keep the subject. Select Create draft.
The new draft has the same content and audience, aimed at the contacts who were sent the campaign and did not open it. A machine open does not count as an open. The draft is marked as a resend of the earlier campaign. Review it, then schedule it like any campaign.
Duplicate campaign makes a new draft named Copy of and the original's name, with the same content, audience, A/B settings and versions, and none of the results.
Limits and errors
Section titled Limits and errors| Where | What happens |
|---|---|
| Sending is not ready | The dialog or the API names the reason: not_set (no sending domain), pending_dns (records missing), no_address (no postal address), paused, staff_paused or daily_limit. Set up your sending domain and warm-up says what to do for each. daily_limit stops a send now, but not a scheduled send or a resume. |
| The checks found an error | Scheduling answers 422 with checks_failed and the issues. Fix each error and schedule again. |
| The campaign has started | A change answers 409 with campaign_locked. Duplicate it. |
| Delete a campaign | Only a draft or a cancelled campaign can be deleted. A cancelled campaign that already sent some emails stays as the record of who got them (campaign_sent). |
| Test sends | 20 requests an hour for each person or key, and up to five addresses in each. |
| Audience | Only subscribed contacts that are not on the suppression list, in the campaign's own mode. A store in test mode can mail test contacts only. |
Result
Section titled Result- A campaign you send is Sending, then Sent, and its report fills in as emails arrive and readers act.
- Orders that follow a click appear under Orders and Revenue, and in the campaigns table on Overview.
Troubleshooting
Section titled Troubleshooting- Schedule… is missing
- Scheduling and sending need an owner or admin. A member with Tier 3 support can write the campaign and send tests, and the page says so.
- The dialog says Sending is not ready
- Open Sending and follow the reason. A campaign scheduled for later and not ready at its time pauses with the reason in the report.
- The checks say there are no recipients
- No subscribed contact is on the lists and segments you chose, or every one is excluded. Check the estimate on Setup, and that the contacts are of the campaign's own mode.
- The open rate looks lower than other tools show
- We leave machine opens out. The report shows how many there were.
- A test never arrives
- The address may be on the suppression list, or sending is not ready. The dialog lists skipped addresses.
- The campaign paused by itself
- Its bounce rate or spam report rate went over your limits, or its time came while sending was not ready. The report shows the reason on the campaign.
Related
Section titled Related- Set up email marketing for your store
- Manage your email contacts, lists and segments
- Set up your sending domain and warm-up
- Create promotions and issue gift cards
With the API
Section titled With the APIThe routes are under https://api.coritan.com/api/v1/orgs/{org_slug}/marketing/. Reading takes marketing.campaigns:read, writing and test sends take marketing.campaigns:write, and scheduling, sending, pausing, resuming, cancelling and resending take marketing.campaigns:send (API key scopes).
Write a campaign
Section titled Write a campaignPOST /campaigns starts a draft and answers 201 with {campaign}. Only name is required:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/campaigns" \
-H "X-API-Key: $ORG_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "October restock", "subject": "New games are in, {{ first_name|default(\"there\") }}",
"preheader": "Fresh stock on the shelves.", "list_ids": [3], "exclude_segment_ids": [8],
"design": {"settings": {"content_width": 600}, "blocks": [
{"id": "title", "type": "heading", "text": "New in the shop", "level": 1},
{"id": "cta", "type": "button", "label": "Shop now", "href": "{{ shop_url }}"},
{"id": "footer", "type": "footer", "show_address": true, "show_unsubscribe": true}]}}'
| Field | What it takes |
|---|---|
name |
Up to 150 characters. |
subject, preheader |
Up to 200 characters. Merge tags work. |
from_name, from_local, reply_to |
The sender. Your sending settings' values when left out. |
template_id |
A saved design whose blocks are copied in. A design in the same request wins. |
design |
{"settings": {...}, "blocks": [Block]}. Every block has an id and a type. A refused design answers 422 with a field such as design.blocks[3].href and a block_id. |
list_ids, segment_ids |
Where to send. With neither, every subscribed contact. Every id must be yours. |
exclude_list_ids, exclude_segment_ids |
Contacts to leave out. |
ab |
{enabled, test_percent (10 to 50), winner_metric (opens, clicks or revenue), winner_after_hours (1 to 72)}. null turns the test off. |
throttle_per_minute |
Emails a minute for this campaign. |
track_opens, track_clicks |
true when left out. |
utm |
{campaign, content}. |
livemode |
The store's own mode when left out. A test-mode store can mail test contacts only. |
A campaign is {id, name, status, livemode, subject, preheader, from_name, from_local, from_address, reply_to, template_id, design, list_ids, segment_ids, exclude_list_ids, exclude_segment_ids, ab, variants, schedule_at, throttle_per_minute, track_opens, track_clicks, utm, parent_id, recipients_count, started_at, completed_at, paused_reason, stats, created_at, updated_at}. status is draft, scheduled, sending, paused, sent or canceled.
stats holds queued, sent, delivered, bounced, complained, failed, unsubscribed, opens, unique_opens, machine_opens, clicks, unique_clicks, the rates open_rate, click_rate, click_to_open_rate, bounce_rate, complaint_rate and unsubscribe_rate as numbers from 0 to 1, orders, revenue in minor units, and currency.
GET /campaignslists campaigns, newest first, as{total, limit, offset, campaigns}.statusfilters andqsearches the name.GET /campaigns/{campaign_id}reads one.PATCHchanges the fields you send.DELETEremoves a draft or a cancelled campaign.PUT /campaigns/{campaign_id}/variantstakes[{key, subject, preheader, from_name, design}]for versionsA,BandC. Version A is the campaign's own. An empty list removes B and C. Turn the test on withab.POST /campaigns/{campaign_id}/duplicateanswers a new draft.POST /campaigns/audience/estimatetakeslist_ids,segment_ids,exclude_list_ids,exclude_segment_idsandlivemode, and answers{count}.
Check, test and send
Section titled Check, test and sendPOST /campaigns/{campaign_id}/checks answers {issues, recipients, spam_score, ready}. An issue is {level, code, message, block_id}, with level error, warning or info. ready is false while any issue is an error.
POST /campaigns/{campaign_id}/test takes {"emails": ["owner@example.com"], "variant": "B"} and answers {sent, skipped}.
POST /campaigns/{campaign_id}/schedule sends now, or at schedule_at as an ISO 8601 time. A time that has passed, or null, sends now. A person must have confirmed it is them in the last 10 minutes, and without it the answer is 403 with reauth_required. An API key with marketing.campaigns:send is not asked.
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/campaigns/41/schedule" \
-H "X-API-Key: $ORG_API_KEY" -H "Content-Type: application/json" \
-d '{"schedule_at": "2026-10-20T09:00:00Z"}'
The answer is {campaign}. Asking again for a campaign that is already sending changes nothing. The refusals:
| Answer | Meaning |
|---|---|
409 with sending_not_ready and a reason |
not_set, pending_dns, no_address, paused, staff_paused or daily_limit. |
422 with checks_failed and issues |
The checks found an error. |
409 with campaign_locked |
The campaign is paused, sent or cancelled. |
422 with invalid on ab |
An A/B test needs a version B. |
POST /campaigns/{campaign_id}/pause takes an optional reason and answers {campaign}. POST .../resume continues a paused campaign. POST .../cancel answers {campaign, skipped}. POST .../resend-unopened takes an optional subject_prefix and answers 201 with a new draft that has parent_id set. Each answers 409 with invalid_status when the campaign is in another status.
Reports
Section titled ReportsGET /campaigns/{campaign_id}/report?granularity=hour answers the campaign, its stats, a timeline of {at, opens, clicks}, the links with each one's share, the devices, clients and countries as {key, count}, the bounces by reason, and for an A/B test the ab versions with the winner marked. granularity is hour or day.
GET /campaigns/{campaign_id}/recipients lists who the campaign was queued for. status takes a send status (queued, sending, sent, delivered, deferred, bounced, complained, failed, suppressed or skipped) or opened, clicked or unsubscribed, and q searches addresses. A recipient is {send_id, contact_id, email, variant, status, error, scheduled_at, sent_at, delivered_at, first_open_at, first_click_at, opens, machine_opens, clicks, orders, revenue, bounced_at, complained_at, unsubscribed_at}.
GET /reports/campaigns?since&until is a table of every campaign that has started, newest first, with its stats. Without a span it covers all time.
Designs
Section titled DesignsGET /templates lists saved designs, ?kind=campaign or automation. POST /templates saves one from name, kind, design, subject and preheader. GET, PATCH and DELETE /templates/{template_id} read, change and delete one. GET /templates/presets answers the starter designs as {presets: [{key, name, description, design, subject, preheader}]}.
POST /render draws a design without saving it. Send design, and optionally subject, preheader, a sample contact and dark: true. The answer is {html, text, issues}, with html_dark when you asked for it.