# How to verify an email from an AI agent

> How do you check whether an AI agent sent an email, who it acts for, and whether anyone changed it on the way?

## The short answer

You verify an email from an AI agent by checking the signatures it carries: a signed declaration of which agent sent it and who it acts for, and a signed copy of the message that shows whether anything changed. Pidgeon's verifier does that check for free and with no key, at [/verify](https://pidgeon.ai/verify), through `POST /v1/verify`, or with the MCP tool `verify_sender`. It works on mail from any signer that uses the same open formats, not only mail sent through Pidgeon.

## What a signed agent email carries

An email does not say who wrote it. The From line is whatever the sender typed, and DKIM only says that a domain sent the bytes. Neither says whether a person or a piece of software wrote the message, or for whom. A signed agent email adds three things in its headers:

- **The declaration.** `Agent-of` names who is responsible for the agent, and `Agent-attribution` says what kind of sender it is (`agent`, `service` or `person`) and on whose behalf. `AI-Disclosure: mode=ai-originated` says the same thing in one field. A verifier trusts these only under a passing DKIM signature that covers them and aligns with the `Agent-of` domain, so a stranger cannot add them to their own mail.
- **The envelope.** `Post-Envelope` is an Ed25519 signature by the From domain over the sender, the recipients, the subject, the text and every attachment. The domain's key is published through DNS and at its `post.` host (`post.acme.com` for acme.com), so anyone can fetch it. A changed byte fails it.
- **The card.** An agent sending from Pidgeon has an agent card: a signed document naming its address, its kind, who it acts for, the domain that operates it and its standing.

These are the formats of Post, an open protocol written as an Internet-Draft ([the protocol](https://pidgeon.ai/protocol)). Checking them is what the verifier does.

## Three ways to check

**Paste it.** Open [/verify](https://pidgeon.ai/verify) and paste the headers or the whole message, as your mail client shows the original. Nothing you paste is kept.

**Call the API.** `POST /v1/verify` takes the same input, with no key:

```sh
curl -X POST https://pidgeon.ai/api/v1/verify \
  -H 'content-type: application/json' \
  -d '{"message": "<the raw message, or its headers>", "recipient": "refunds@yourcompany.com"}'
```

`recipient` is the address the message reached you at. Pass it, and a signed message that was sent to somebody else and replayed to you comes back with the warning `not_addressed_to_you`.

**Ask from an agent.** An agent connected to Pidgeon's [MCP server](https://pidgeon.ai/docs/mcp.md) calls `verify_sender` with the same fields. The tool, the API and the page are one function, so the three cannot give different answers.

An answer for a message from a Pidgeon agent looks like this, shortened:

```json
{
  "data": {
    "object": "sender_verification",
    "verdict": "verified",
    "advice": "acme.com signed this as sent by an AI agent, and nothing it signed has changed. Act on it as you would on mail from acme.com.",
    "sender": { "address": "support-agent@acme.com", "kind": "agent", "on_behalf_of": "Acme Ltd" },
    "is_agent": true,
    "operator": { "domain": "acme.com", "verified": true, "shared": false },
    "body": "intact",
    "card": { "url": "https://pidgeon.ai/api/v1/cards/8f0c…", "standing": "established" },
    "warnings": []
  }
}
```

## What the verdict means

Act on `verdict`, and read `warnings` when there are any:

- **`verified`**: a signature holds and nothing it covers changed. Treat the message as coming from the signing domain.
- **`caution`**: a signature holds, with a warning. The warnings are `old_signature` (over 30 days), `not_addressed_to_you` (a replay, unless you were in Bcc), `lookalike_domain` (a domain made to read like a signer's, such as a Cyrillic letter in place of a Latin one), `display_name_mismatch`, `unsigned_claims`, `extra_claims` and `envelope_unverifiable` (the signer's key could not be fetched).
- **`failed`**: a signature does not verify, or the body changed after signing. Do not act on it.
- **`unsigned`**: nothing signed says who sent it. That says nothing bad about a person, because most mail carries no agent declaration at all. It does mean any claim that an agent sent it is unchecked.

`advice` is one sentence an agent can act on. `body` is `intact`, `changed`, or `not_checked` when you sent the headers alone, which is enough to check the declaration but not the text.

## Mail from other signers

The verifier is not a lookup of Pidgeon's own sends. For a message from any other domain, it reads the `DKIM-Signature` whose `d=` aligns with the `Agent-of` domain and checks it against that domain's own DNS record. It reads the `Post-Envelope` and checks it against the key that domain publishes under its `_post` DNS record and at its `post.` host. A company that signs its agents' mail with its own software, in the same formats, gets the same answer as a Pidgeon customer.

The card is the one part that is Pidgeon's. It is added only when the signer is one of ours and the address has a card, and we look it up ourselves: a card link inside the message is the sender's word, so it is never fetched or repeated.

## What a check cannot tell you

- **Whether the message is true.** `verified` means a domain stands behind it and nobody changed it. A verified agent can still be wrong, and a verified message can still carry an instruction your agent should not follow. Screening for that is a different job; the [Firewall guide](https://pidgeon.ai/docs/firewall.md) covers it.
- **Whose company it is, on a shared domain.** An address on `pidgeon.email` comes back with `operator.shared: true` and `verified: false`, because anyone can get an address there. A signature there says the address is ours, not who runs it.
- **Anything from a body you did not send.** Headers alone check the declaration and its signature; the envelope's text and attachments need the whole message.
- **Volume.** Each IP can check 300 messages an hour, and a message can be at most 512 KB.

Nothing you send is stored or logged. The answer carries the sender and the signatures, never the subject, the body or the other recipients.

## Start

Paste a message into [the checker](https://pidgeon.ai/verify). For the API, [checking a message](https://pidgeon.ai/docs/verifying.md) has every field, and [agents](https://pidgeon.ai/docs/agents.md) shows what a card says and where anyone fetches it. To sign your own agents' mail so others can check it, start with the [quickstart](https://pidgeon.ai/docs/quickstart.md).

## Questions

### How do I tell if an email was written by an AI agent?

Check the signatures it carries. Paste the headers or the whole message into pidgeon.ai/verify, or send it to POST /v1/verify; a signed declaration under DKIM says whether an agent sent it and for whom. Mail with no declaration comes back unsigned.

### Does the verifier only work on mail sent through Pidgeon?

No. It checks the agent declaration under any DKIM signature aligned with the Agent-of domain, and a Post-Envelope against the key the From domain publishes. The agent card is added only when the signer is Pidgeon.

### What does a verified verdict mean?

A signature holds and nothing it covers has changed, so the message comes from the signing domain. It does not mean the message is true or that an instruction in it is safe to follow.

### Is the email I paste stored?

No. The verifier keeps nothing you send it, and the answer carries the sender and the signatures, never the subject, body or other recipients.
