Skip to main content

Squillify development is paused indefinitely. Read why, and discover what this experiment achieved. Read the story

MicroSkills Reference

Email Header Validator

communication.email_validate_headers

ProductionBounded custom (Class B)Deterministicstrictinstantv1.0.0
No network

Overview

Nothing here is sent and nothing is connected to. We read the headers you hand us and check their syntax and your policy, and that is the whole of it: we cannot tell you whether a domain exists, whether a mailbox is real, or whether your message will be delivered. The word validate invites exactly that assumption, so we say it plainly — no name is resolved, no mail exchanger is looked up, and a clean report means the headers are well formed, not that the mail will arrive. What you do get is three kinds of finding. What is missing, judged against a profile you pick or a list you supply. What is malformed: a value that carries a carriage return and therefore injects a second header, a date nothing can read, a message identifier with no domain half, a header that appears twice when it may appear once. And what is risky: a display name carrying a different address from the real one, a reply that would leave your domain, a blind copy alongside visible recipients, more recipients than you allow, and bulk mail with no way to unsubscribe. Every finding carries a severity, and a report is hygiene and policy — never a spam verdict and never an authentication result.

A header value that carries a carriage return does not look wrong until a receiving server reads everything after it as a header you never wrote, which is how a blind copy gets added to somebody else's message. Around that one check sit the quieter problems that cost you delivery rather than security: a date nothing can parse, a message identifier with no domain so threading breaks, two From headers so nobody agrees who sent it, bulk mail with no way out. All of them are decidable from the headers alone, and all of them are cheaper to catch here than in a postmaster's reply. Address syntax comes from the same engine our email-syntax skill uses, so an address judged valid here is valid everywhere on the platform.

Reliability

The same headers and the same rules always produce the same report, in the same order, and no clock or network is involved anywhere. Names are matched without regard to case. A header name longer than 256 characters or a value longer than 8192 is reported as a finding rather than refused, so you still get the rest of the report. Findings never carry an address, a subject or a body — only a header name, a position, a count and a limit — and nothing you send is written to a log. Four things stop the call instead of answering it. HEADERS_INVALID means the set could not be read as headers at all: an entry that is not an object, an entry with a property other than name and value, an empty name, a value that is not a string, or an empty set — fix the shape named in the message and retry, because the same payload will always be refused; HEADERS_LIMIT_EXCEEDED means more than 256 headers, or more header text in total than one request may carry — send the headers you actually want checked; HEADER_PROFILE_UNSUPPORTED means profile was something other than TRANSACTIONAL, MARKETING or MINIMAL, and we refuse rather than quietly fall back to the default, because a message checked under rules nobody chose is worse than no check — correct the value and retry; HEADER_RULES_INVALID means the rules object named a property we do not implement, a rule was not a boolean, maxRecipients was outside 1 to 100000, or one of the two header-name lists was too long or held something that is not a usable name — correct the rule and retry. A malformed or risky header set is never one of these: it is a normal success with valid false and the findings inside.

Context footprint

A clean set answers in well under a kilobyte. A report grows with the number of findings and stops at maxIssues, and the counts in summary stay complete even when the list is cut, so you always know how much you did not see. A successful call costs 1 execution credit.

When to use it

Before a message is handed to a mail server, whenever any part of a header was built from something a person typed, and in the build that ships your outbound templates.

When you need to know an address is deliverable, when you want a spam or authentication verdict, or when you have a raw message rather than a header set.

Scope

Note
Reports what is missing, malformed or risky in a set of email headers, judged from the headers alone.

Non-goals

  • Does not send, queue or relay a message, and opens no connection of any kind
  • Does not resolve a domain, look up a mail exchanger, or confirm a mailbox exists, so a clean report never means the mail will be delivered
  • Does not check SPF, DKIM or DMARC — those need name resolution and cryptographic verification, and we do neither
  • Does not score a message for spam, and a clean report is not a reputation verdict
  • Does not repair a header, reorder a set, or hand back a corrected message
  • Does not read a message body or an attachment

Properties

IdempotentRetry safeStreaming supportedFile input supportedRequires external service

Capabilities

analysisvalidation

Cost & billing

Flat rate per invocation

MetercallsBillable onsuccessCost per call1 credit

File handling

Accepts file_refProduces file_refMax input2 MB

How we handle your data

Sensitive input, safe output
What this means

This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.

Squillify guarantees
  • Runs inside Squillify
  • This skill runs inside Squillify.
  • Payloads are not sent to an external service by this skill.
  • Squillify does not permanently store submitted payloads.
  • Telemetry records metadata only, not submitted content.
  • Submitted payloads are not used for training.
  • Submitted payloads are not reviewed by humans.
  • This skill does not target sensitive data.
  • The response does not contain submitted content.
Your responsibility

Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.

Read the Microskill Data Handling Policy

Input schema

FieldTypeRequiredDescription
headersarrayYesBounded ordered array of header name/value declarations. The item shape is defined by the nested schema and the array contains from 0 through 10000 items.
profilestringNoNamed header profile; omit it for standard.
requiredHeadersarrayNoBounded optional required-header names; omit it to use profile requirements. The item shape is defined by the nested schema and the array contains from 0 through 10000 items.
forbiddenHeadersarrayNoBounded optional forbidden-header names; omit it to use profile restrictions. The item shape is defined by the nested schema and the array contains from 0 through 10000 items.
rulesobjectNoClosed header-policy option object; omit it for profile defaults. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items.
maxIssuesintegerNoIssue cap; omit it for one thousand.

Output schema

FieldTypeRequiredDescription
validbooleanYesWhether no error-severity header issue exists.
issuesarrayYesBounded header issue receipts in deterministic rule order. The item shape is defined by the nested schema and the array contains from 0 through 10000 items.
summaryobjectYesClosed header, error, warning, and truncation summary. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items.
resolvedRulesobjectYesClosed applied header rules with defaults filled in. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items.

API contract

POST /v1/execute
curl
curl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"communication.email_validate_headers","input":{"headers":[{"name":"From","value":"Northwind Orders <[email protected]>"},{"name":"To","value":"[email protected]"},{"name":"Date","value":"Tue, 28 Jul 2026 09:15:00 +0200"},{"name":"Message-ID","value":"<[email protected]>"}],"profile":"TRANSACTIONAL"}}'
Integrations
TypeScript SDK
import { Squillify } from "@squillify/sdk";

const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });

const { output } = await sq.execute("communication.email_validate_headers", {"headers":[{"name":"From","value":"Northwind Orders <[email protected]>"},{"name":"To","value":"Alice Whitfield <[email protected]>"},{"name":"Subject","value":"Your order 4417 has shipped"},{"name":"Date","value":"Tue, 28 Jul 2026 09:15:00 +0200"},{"name":"Message-ID","value":"<[email protected]>"}],"profile":"TRANSACTIONAL"});

Errors

CodeHTTPRetryableDescription
VALIDATION_ERROR400NoThe request payload failed schema validation.
UNAUTHORIZED401NoMissing or invalid API key.
SKILL_NOT_ACCESSIBLE403NoThe API key is not granted access to this skill.
SKILL_NOT_AVAILABLE_FOR_PLAN403NoThis skill is not included in your current plan.
PLAN_ELIGIBILITY_UNAVAILABLE503YesPlan eligibility could not be decided. Retry after backoff.
SKILL_NOT_FOUND404NoThe requested skill or version does not exist.
TENANT_RATE_LIMITED429YesRequest rate limit exceeded. Retry after backoff.
QUOTA_EXCEEDED429YesPlan quota exhausted. Retryable once the quota period resets.
EXECUTION_TIMEOUT504YesExecution exceeded the allowed time budget.
HEADERS_INVALID400NoThe request payload failed this skill's input validation.
HEADERS_LIMIT_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
HEADER_PROFILE_UNSUPPORTED400NoThe skill does not support the requested variant.
HEADER_RULES_INVALID400NoThe request payload failed this skill's input validation.

Other limits

LimitValue
Recommended inline bytes32 KiB

Limitations

  • Does not send, queue or relay a message, and opens no connection of any kind
  • Does not resolve a domain, look up a mail exchanger, or confirm a mailbox exists, so a clean report never means the mail will be delivered
  • Does not check SPF, DKIM or DMARC — those need name resolution and cryptographic verification, and we do neither
  • Does not score a message for spam, and a clean report is not a reputation verdict
  • Does not repair a header, reorder a set, or hand back a corrected message
  • Does not read a message body or an attachment
  • Deciding whether an address can receive mail, which needs the network
  • Judging whether a message is spam or whether its sender is authenticated
  • Parsing a whole raw message into headers, body and attachments

How to call

POST /v1/execute (requires authentication)

Request bodyjson
{
  "skillKey": "communication.email_validate_headers",
  "input": {
    "headers": [
      {
        "name": "From",
        "value": "Northwind Orders <[email protected]>"
      },
      {
        "name": "To",
        "value": "Alice Whitfield <[email protected]>"
      },
      {
        "name": "Subject",
        "value": "Your order 4417 has shipped"
      },
      {
        "name": "Date",
        "value": "Tue, 28 Jul 2026 09:15:00 +0200"
      },
      {
        "name": "Message-ID",
        "value": "<[email protected]>"
      }
    ],
    "profile": "TRANSACTIONAL"
  }
}

Examples

A clean transactional header set passes with nothing to report

Inputjson
{
  "headers": [
    {
      "name": "From",
      "value": "Northwind Orders <[email protected]>"
    },
    {
      "name": "To",
      "value": "Alice Whitfield <[email protected]>"
    },
    {
      "name": "Subject",
      "value": "Your order 4417 has shipped"
    },
    {
      "name": "Date",
      "value": "Tue, 28 Jul 2026 09:15:00 +0200"
    },
    {
      "name": "Message-ID",
      "value": "<[email protected]>"
    }
  ],
  "profile": "TRANSACTIONAL"
}
Expected outputjson
{
  "valid": true,
  "summary": {
    "profile": "TRANSACTIONAL",
    "truncated": false,
    "bySeverity": {},
    "headerCount": 5,
    "recipientCount": 1,
    "issueCount": 0
  },
  "resolvedRules": {
    "maxIssues": 1000,
    "checkListUnsubscribe": false,
    "requireTo": true,
    "requireFrom": true,
    "checkAddressSyntax": true,
    "forbidBccWithRecipients": false,
    "forbiddenHeaders": [],
    "requireDate": true,
    "requiredHeaders": [
      "From",
      "To",
      "Date",
      "Message-ID"
    ],
    "requireMessageId": true
  },
  "issues": []
}
Tip
All four headers the transactional profile asks for are present, both addresses pass the same syntax rules our email-syntax skill applies, the date reads cleanly and the message identifier has its domain half. resolvedRules shows exactly what was enforced, so you can store it beside the verdict.

A marketing set with an injected header, a spoofed display name and a reply that leaves the domain

Inputjson
{
  "profile": "MARKETING",
  "rules": {
    "maxRecipients": 50
  },
  "headers": [
    {
      "name": "From",
      "value": "\"Northwind Billing <[email protected]>\" <[email protected]>"
    },
    {
      "name": "To",
      "value": "[email protected]"
    },
    {
      "name": "Reply-To",
      "value": "[email protected]"
    },
    {
      "name": "Subject",
      "value": "Your July statement is ready\r\nBcc: [email protected]"
    },
    {
      "name": "Date",
      "value": "yesterday afternoon"
    },
    {
      "name": "Message-ID",
      "value": "<statement-july-2026>"
    }
  ]
}
Expected outputjson
{
  "valid": false,
  "summary": {
    "profile": "MARKETING",
    "truncated": false,
    "bySeverity": {
      "ERROR": 3,
      "WARNING": 3
    },
    "headerCount": 6,
    "recipientCount": 1,
    "issueCount": 6
  },
  "resolvedRules": {
    "requireDate": true,
    "requireTo": true,
    "requireFrom": true,
    "requiredHeaders": [
      "From",
      "To",
      "Date",
      "Message-ID"
    ],
    "requireMessageId": true,
    "checkListUnsubscribe": true,
    "forbiddenHeaders": [],
    "forbidBccWithRecipients": false,
    "maxRecipients": 50,
    "checkAddressSyntax": true,
    "maxIssues": 1000
  },
  "issues": [
    {
      "index": 3,
      "code": "HEADER_CONTROL_CHARACTER",
      "header": "Subject",
      "severity": "ERROR",
      "detail": "A carriage return, line feed or other control character sits at position 28 of the value. A receiving server reads that as the end of the header and the start of another one, which is how a header is injected."
    },
    {
      "index": 0,
      "code": "DISPLAY_NAME_SPOOF",
      "header": "From",
      "severity": "WARNING",
      "detail": "The display name contains an address of its own that is not the address the message is really from. A reader sees the display name and almost never the real one."
    },
    {
      "index": 2,
      "code": "REPLY_TO_DOMAIN_MISMATCH",
      "header": "Reply-To",
      "severity": "WARNING",
      "detail": "A reply to this message leaves the domain the message came from. That is normal for a helpdesk and it is also what a redirected reply looks like, so it is worth a look."
    },
    {
      "index": 5,
      "code": "MESSAGE_ID_INVALID",
      "header": "Message-ID",
      "severity": "ERROR",
      "detail": "A message identifier is a local part and a domain inside angle brackets. Without the domain half it is not globally unique, so threading and duplicate detection stop working."
    },
    {
      "index": 4,
      "code": "DATE_UNPARSEABLE",
      "header": "Date",
      "severity": "ERROR",
      "detail": "The value is not a date in the form a message uses, which looks like Tue, 28 Jul 2026 09:15:00 +0200. We never fall back to the current time, so an unreadable date stays unreadable."
    },
    {
      "code": "LIST_UNSUBSCRIBE_MISSING",
      "severity": "WARNING",
      "header": "List-Unsubscribe",
      "detail": "Bulk mail without a one-click way out is filtered harder by the large mailbox providers, and in several places it is also a legal requirement."
    }
  ]
}
Tip
Six different findings at once. The subject carries a carriage return and a whole second header behind it; the display name shows a billing address the message is not really from; a reply would go to a different domain; the date cannot be read; the message identifier has no domain; and a marketing message has no List-Unsubscribe. Three of the six are errors and three are warnings, and only the errors make valid false.
  • Stop a header-injection attempt before a message is handed to a mail server, by refusing any value that carries a carriage return or a line feed
  • Gate an outbound template in your build, so a missing Message-ID or an unparseable Date is caught long before a customer sees it
  • Check a bulk campaign carries a List-Unsubscribe and stays inside your recipient policy
  • Spot a display name carrying somebody else's address while reviewing suspicious mail

Avoid for

  • Deciding whether an address can receive mail, which needs the network
  • Judging whether a message is spam or whether its sender is authenticated
  • Parsing a whole raw message into headers, body and attachments

Composable with

Safety & privacy

  • Runs without calling any external service.
  • Your payloads are never stored permanently.
  • Telemetry captures metadata only — never your payload contents.
  • We never train models on your payloads.
  • No human reviews your payloads.

Tags

emailmessagingsecurityvalidation