Preflight a campaign
/api/v1/campaigns/{campaign}/preflightChecks a campaign for the problems that would stop it — unverified domain, missing unsubscribe, empty audience, insufficient allowance — without sending anything.
Once Bounce protection is on for your account it also says how many recipients were never
checked (unchecked_count, and about what share looks bad, likely_bad_percent) and how
many the send will skip because their inbox is full (inbox_full_count, with the latest
end of those holds in inbox_full_until). Skipped recipients are still in
recipient_count; their recipient status reads skipped_inbox_full.
If your marketing is stopped because too many emails bounced, the blocker bounce_stop
says so, and its message says how to start again.
This endpoint takes no request body.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
campaignrequired | path | string | The campaign identifier. |
Request
/api/v1/campaigns/{campaign}/preflightcurl -X POST 'https://app.mailyte.com/api/v1/campaigns/01JBT8XQ2M9WYC3K4F6R7S8T9V/preflight' \
-H 'Authorization: Bearer mk_live_YOUR_API_KEY'const response = await fetch('https://app.mailyte.com/api/v1/campaigns/01JBT8XQ2M9WYC3K4F6R7S8T9V/preflight', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_YOUR_API_KEY',
},
});
const { data } = await response.json();import requests
response = requests.post(
"https://app.mailyte.com/api/v1/campaigns/01JBT8XQ2M9WYC3K4F6R7S8T9V/preflight",
headers={"Authorization": "Bearer mk_live_YOUR_API_KEY"},
)
data = response.json()["data"]<?php
$response = Http::withToken('mk_live_YOUR_API_KEY')
->post('https://app.mailyte.com/api/v1/campaigns/01JBT8XQ2M9WYC3K4F6R7S8T9V/preflight');
$data = $response->json('data');require "net/http"
require "json"
uri = URI("https://app.mailyte.com/api/v1/campaigns/01JBT8XQ2M9WYC3K4F6R7S8T9V/preflight")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer mk_live_YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }Response
Success.
dataobjectWhether the campaign can go out now, and what is stopping it. Readiness depends on your account, the sender, the audience and today's limits, so check it just before sending. Only drafts are checked.
objectstringcampaign_preflightcampaign_idstringstatestringdraft | scheduled | sending | paused | sent | canceled | unknownThe campaign's lifecycle state, from the same map
campaign.stateuses.sendablebooleanThe verdict. Exactly
blockers === [], computed in the same expression, and kept because "is there anything in this array" is a worse thing to have to write.blockersarray<object>Everything stopping this campaign. Empty means it can be sent. Branch on
code:POST /sendandPOST /schedulegive only the message.codestringnot_draft | marketing_review_pending | sender_unverified | no_content | no_unsubscribe | no_audience | warmup_exceeded | marketing_allowance | scheduled_sends | bounce_stop | sample_contentWhy the campaign cannot go out. -
not_draft: only a draft can be scheduled. -marketing_review_pending: your account's marketing application is not approved yet. -sender_unverified: the sender is not verified. -no_content: no subject and HTML body, and no template. -no_unsubscribe: the HTML has no unsubscribe link. -no_audience: no list or segment chosen. -warmup_exceeded: more recipients than today's warm-up limit allows. -marketing_allowance: more recipients than your marketing emails left this month. Scheduling answers 402 and names the plan that covers it. -scheduled_sends: a send time on a plan without scheduling. -bounce_stop: your marketing is stopped because too many emails bounced;messagesays how to start again. -sample_content: the email still has a gallery design's sample text, or a link or image to a placeholder address such as example.com;messagenames what to change.fieldstringWhich part of the campaign the code is about.
messagestringHuman-readable prose. Not stable — branch on
code.
warningsarray<object>Worth knowing, does not stop the send.
codestringinsufficient_creditsinsufficient_credits: your balance is below the number of recipients. A warning, not a blocker: scheduling still works.fieldstringWhich part of the campaign the code is about.
messagestringHuman-readable prose. Not stable — branch on
code.
recipient_countintegerHow many contacts the campaign would go to now, counting each person once and leaving out those who cannot be emailed.
credit_balanceintegerWhole sends, not money: one credit is one recipient. The organization's balance in currency belongs to billing.
personalizationobjectvariablesarray<object>Which template variables the content uses. Called
variables, nottags, because this API already usestagsfor the send-time correlation labels stamped on every message.namestringThe variable's name, without braces.
systembooleanTrue for variables that always have a value when sending:
email,sender_name,sender_email,organization_nameandunsubscribe_url. Theirmissingis always 0.nameis not one of them, because a contact may have no name.missingintegerRecipients in the audience with no value for it. A count, not a rate —
recipient_countis the denominator if you want one.defaultstringThe organization's contact-field default, which fills a blank at send time. A variable with a default is never actually empty, so a non-zero
missingbeside a non-nulldefaultis informational rather than a problem.
has_unsubscribebooleanWhether the content satisfies the compliance gate that
no_unsubscribeenforces. The blocker is the refusal; this is the observation, and both read the same check.
warmupobjectToday's warm-up limit for the sending address. Null when your account is not warming up a dedicated IP (most accounts), or when the limit could not be read.
dayintegerWhich day of the warm-up curve this is. Never null when the enclosing
warmupobject is present — the snapshot that builds it either has every figure or is itself null.ceilingintegerHow many recipients today's warm-up allows.
sent_todayintegerRecipients already sent from this address today.
remainingintegerHow many more recipients today's warm-up allows:
ceilingminussent_today, never below 0.recipientsintegerWhat THIS campaign would add — the same projection as the top-level
recipient_count. Never null when the enclosingwarmupobject is present.completes_onstringThe date warm-up finishes. Null when it cannot be worked out.
unchecked_countintegerRecipients whose address was never checked, counted once each, out of
recipient_count.POST /campaigns/{id}/check-recipientschecks them, one check each. Always 0 while Bounce protection is off.likely_bad_percentintegerRoughly what percent of
recipient_countlooks bad among the unchecked recipients, from a free estimate. Null when there are too few to estimate (under 10), while the estimate is being made, when it cannot be made, or while Bounce protection is off.inbox_full_countintegerRecipients whose inbox was full when last checked. Marketing to them is held for 7 days, so the send skips them and uses no credit for them. Counted as of the send time.
recipient_countstill includes them. Always 0 while Bounce protection is off.inbox_full_untilstringThe latest time any of the
inbox_full_countholds ends.nullwheninbox_full_countis0.
Returned inside the standard envelope.
Errors
| Status | When |
|---|---|
401 | The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist. |
403 | The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys. |
404 | No such resource in this organization. |
422 | The request was understood but the values were not acceptable. |
429 | Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait. |
Every status, with what causes it and what to do, is on the error reference.