Run Assertions
Evaluate CI assertions against a captured message.
Run each check against the captured message and its analysis. The request returns HTTP 200 even when one or more checks fail. The top-level pass is true only when every result passes, and results remain in request order.
Scope: email_testing.messages.read
Path Parameters
idstringrequiredThe ID of the captured message, for example d5949378-3069-4d8f-a8f3-6e5edf5d6060. You get it from List Messages or the message.received webhook. It isn't the email's Message-ID header.
Request Body
assertionsobject[]requiredThe checks to run against the message. Include at least one assertion.
Each assertion object has a required type string and a required value. The value's JSON type depends on the assertion.
Operators
| Suffix | Passes when |
|---|---|
_lte | The actual value is less than or equal to the supplied value. |
_gte | The actual value is greater than or equal to the supplied value. |
_eq | Values are equal; strings compare case-insensitively and booleans compare exactly. |
_contains | The actual string contains the supplied substring, case-insensitively. |
_absent | The named symbol did not fire. |
_clean | The supplied boolean matches whether nothing is listed: true means none are listed and false means at least one is listed. |
Numbers may be JSON numbers or numeric strings such as "5.5". Booleans must be JSON true or false. String values must be non-empty. Invalid or missing values produce a failed result with bad value.
Assertion Types
Spam score
| Type | Value | Passes when | Needs |
|---|---|---|---|
rspamd_score_lte | Number | The Rspamd score is at or below the value. | A Rspamd result. |
rspamd_action_eq | String | The Rspamd action equals the value, case-insensitively. Values: no action, greylist, add header, rewrite subject, soft reject, reject. | A Rspamd result. |
AI Analysis
| Type | Value | Passes when | Needs |
|---|---|---|---|
ai_score_lte | Number from 0 to 10 | The AI score is at or below the value. | An AI Analysis with status ok; call Run AI Analysis first. |
ai_verdict_eq | clean, low_risk, suspicious, likely_spam, or spam | The verdict equals the value, case-insensitively. clean is a score from 0 to under 2; low_risk is 2–4; suspicious is 4–6; likely_spam is 6–8; spam is 8–10. | An AI Analysis with status ok. |
ai_symbol_absent | A symbol name such as AI_URGENCY | The named symbol did not fire. actual is the list of fired symbol names. | An AI Analysis with status ok. |
Use a symbol name from the complete ruleset:
| Symbol | Description |
|---|---|
AI_LOOKS_LIKE_PHISHING | Reads like a phishing attempt |
AI_SCAM_PATTERN | Follows a known scam pattern such as prizes, easy money, or money transfers |
AI_CREDENTIAL_PROMPT | Asks the reader to sign in or enter personal or payment details |
AI_LINK_TEXT_MISMATCH | Link text shows a different domain than the link target |
AI_BRAND_DOMAIN_MISMATCH | Brand named in the email does not match the From or link domains |
AI_THREATENING_TONE | Threatens loss of access or penalties unless the reader acts |
AI_SPAMMY_WORDING | Relies on phrases commonly associated with spam |
AI_URGENCY | Artificial urgency to act immediately |
AI_HARD_SELL | Hard sell with repeated buy-now calls to action |
AI_SHOUTING | Overuse of capitals, exclamation marks, currency symbols, or emoji |
AI_EXAGGERATED_CLAIMS | Unrealistic or unsubstantiated claims about results, earnings, health, or savings |
AI_SENSITIVE_TOPIC | Topic that spam filters treat with extra suspicion |
AI_SUBJECT_MISMATCH | Subject line does not reflect the body |
AI_FAKE_REPLY_SUBJECT | Pretends to continue a conversation that did not happen |
AI_SUBJECT_SPAMMY | Subject line looks like spam on its own |
AI_WEAK_PREHEADER | Inbox preview text is missing or boilerplate |
AI_NO_OPT_OUT | Bulk email with no way to unsubscribe or manage preferences |
AI_NO_SENDER_IDENTITY | Commercial email that does not identify the sending business |
AI_NO_POSTAL_ADDRESS | Marketing email without a physical postal address |
AI_UNRENDERED_PLACEHOLDER | Template placeholders or variables were not filled in |
AI_TEST_CONTENT | Placeholder, dummy, or test content |
AI_NON_PRODUCTION_LINKS | Links or images point to local, staging, or temporary hosts |
AI_PART_MISMATCH | Plain text and HTML versions carry different content |
AI_BROKEN_CONTENT | Visible raw code, template errors, or garbled characters |
AI_THIN_CONTENT | Very little meaningful text, mostly images or a single link |
AI_HIDDEN_TEXT | HTML contains text hidden from the reader |
AI_VAGUE_LINK_TEXT | Main links use vague text such as 'click here' |
AI_WRITING_ISSUES | Noticeable spelling or grammar mistakes |
AI_TRANSACTIONAL_DETAIL | Contains specific details of a transaction or account action |
AI_CONSISTENT_SENDER | Sender name, domains, and content represent one organisation |
AI_CLEAR_OPT_OUT | Clear unsubscribe option and sender identified in the footer |
AI_PERSONAL_CORRESPONDENCE | Genuine one-to-one correspondence |
Authentication
| Type | Value | Passes when | Needs |
|---|---|---|---|
spf_eq | Status string | The SPF status equals the value, case-insensitively. | Authentication analysis. |
dkim_eq | Status string | The DKIM status equals the value, case-insensitively. | Authentication analysis. |
dmarc_eq | Status string | The DMARC status equals the value, case-insensitively. | Authentication analysis. |
arc_eq | Status string | The ARC status equals the value, case-insensitively. | Authentication analysis. |
auth_aligned_eq | Boolean | The value matches authentication.aligned. | Authentication analysis. |
The authentication statuses are pass, fail, softfail, neutral, none, temperror, and permerror.
authentication.aligned is true when SPF or DKIM passes for a domain that aligns with the From domain, which is what DMARC needs.
Blocklists
| Type | Value | Passes when | Needs |
|---|---|---|---|
links_rbl_clean | Boolean | true means no link host is listed; false means at least one link host is listed. | Analysis; before it exists, the result message is link rbl unavailable. |
ip_rbl_clean | Boolean | true means the sending IP is not listed; false means it is listed. | Analysis; before it exists, the result message is ip rbl unavailable. |
Size & content
| Type | Value | Passes when | Needs |
|---|---|---|---|
size_lte | Number of bytes | The full raw message size, including attachments, is at or below the value. | The captured message; analysis is not required. |
gmail_clip_eq | Boolean | The value matches whether the decoded HTML part is over 102,400 bytes, Gmail's clipping threshold. | The captured message; analysis is not required. |
unsubscribe_valid_eq | Boolean | The value matches whether the message has a List-Unsubscribe header with an HTTPS URL and a List-Unsubscribe-Post header indicating one-click. | Unsubscribe analysis. |
accessibility_score_gte | Number from 0 to 100 | The accessibility score is at or above the value. | Accessibility analysis. |
Subject & recipient
| Type | Value | Passes when | Needs |
|---|---|---|---|
subject_contains | Non-empty string | The subject contains the value as a case-insensitive substring. | The captured message; analysis is not required. |
recipient_eq | Email address; Name <addr> is also accepted. | The To header or any envelope recipient matches, case-insensitively, comparing addresses only. | The captured message; analysis is not required. |
Result Fields
Each result includes type, pass, and the supplied value as expected. actual contains the value found, and message explains why a check couldn't run. Fields with no value are left out.
| Message | Meaning |
|---|---|
unknown assertion type | The assertion type is not supported. |
bad value | The value is missing, empty, or has the wrong JSON type or format. |
rspamd unavailable | No Rspamd result is available. |
ai analysis unavailable | AI Analysis is missing or does not have status ok. |
authentication unavailable | Authentication analysis is missing. |
link rbl unavailable | Analysis is missing for the link-host blocklist check. |
ip rbl unavailable | Analysis is missing for the sending-IP blocklist check. |
unsubscribe unavailable | Unsubscribe analysis is missing. |
accessibility unavailable | Accessibility analysis is missing. |
Analysis runs after capture. Wait for the analysis.completed webhook or poll Get Analysis before asserting, or analysis-dependent checks may return unavailable results.
Sample Request
curl -X POST "https://api.maileroo.com/v1/email-testing/messages/d5949378-3069-4d8f-a8f3-6e5edf5d6060/assertions" \
-H "Authorization: Bearer roo_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d @- <<'JSON'
{
"assertions": [
{"type": "rspamd_score_lte", "value": 5},
{"type": "ai_symbol_absent", "value": "AI_URGENCY"},
{"type": "spf_eq", "value": "pass"},
{"type": "dkim_eq", "value": "pass"},
{"type": "dmarc_eq", "value": "pass"},
{"type": "auth_aligned_eq", "value": true},
{"type": "ip_rbl_clean", "value": true},
{"type": "size_lte", "value": 200000},
{"type": "gmail_clip_eq", "value": false},
{"type": "unsubscribe_valid_eq", "value": true},
{"type": "subject_contains", "value": "Verify"},
{"type": "recipient_eq", "value": "[email protected]"}
]
}
JSONSample Response
The response is wrapped in a data object.
{
"data": {
"pass": false,
"results": [
{
"type": "rspamd_score_lte",
"expected": 5,
"actual": 1.8,
"pass": true
},
{
"type": "ai_symbol_absent",
"expected": "AI_URGENCY",
"actual": ["AI_URGENCY", "AI_HARD_SELL"],
"pass": false
},
{
"type": "spf_eq",
"expected": "pass",
"actual": "pass",
"pass": true
},
{
"type": "dkim_eq",
"expected": "pass",
"actual": "pass",
"pass": true
},
{
"type": "dmarc_eq",
"expected": "pass",
"actual": "pass",
"pass": true
},
{
"type": "auth_aligned_eq",
"expected": true,
"actual": true,
"pass": true
},
{
"type": "ip_rbl_clean",
"expected": true,
"actual": true,
"pass": true
},
{
"type": "size_lte",
"expected": 200000,
"actual": 148213,
"pass": true
},
{
"type": "gmail_clip_eq",
"expected": false,
"actual": false,
"pass": true
},
{
"type": "unsubscribe_valid_eq",
"expected": true,
"actual": true,
"pass": true
},
{
"type": "subject_contains",
"expected": "Verify",
"actual": "Verify your new device",
"pass": true
},
{
"type": "recipient_eq",
"expected": "[email protected]",
"actual": "CI runner <[email protected]>",
"pass": true
}
]
}
}When analysis is missing or a value is invalid, actual is left out and message says why:
{
"data": {
"pass": false,
"results": [
{
"type": "ai_score_lte",
"pass": false,
"expected": 5,
"message": "ai analysis unavailable"
},
{
"type": "subject_contains",
"pass": false,
"expected": "",
"message": "bad value"
}
]
}
}Errors
400 Bad Request— the body is invalid JSON or the assertion list is empty. Unknown assertion types and invalid values are returned as failed results with HTTP 200.404 Not Found— the message does not exist or is not owned by the account.