Agent API and MCP
Track the email your AI agent sends.
The extension puts the pixel in during Gmail's own send. A program that sends through the Gmail API bypasses the browser entirely: no pixel goes out, no links are rewritten, and the message never appears in the dashboard. The agent API closes that gap without giving the agent anything it could misuse.
The gap is narrow and specific
Mail an agent leaves as a draft for you to review and send from Gmail is already tracked: that draft opens as an ordinary compose window and the extension's send hook fires on it. Only mail the agent sends itself is invisible.
Two pieces close it: a REST surface under /agent/* on your own deployment, and a thin MCP server that wraps it. The REST layer is the product; the MCP server is an adapter, so an agent that does not speak MCP is not excluded.
The agent's sequence
whoami, once at start-up
Reports the account address and the linked mailboxes, so an agent can fail at start-up with something a person can act on rather than at send time with a 403 per message.
prepare, immediately before sending
Takes the mailbox, recipients and HTML body; returns the body with typed URLs linkified, eligible links signed through your tracking domain, the disclosure line added if you use one, and the pixel appended. A policy refusal (every recipient is you, or a recipient is on the do-not-track list) returns
tracked: false, a plain-English reason, and your own HTML byte-for-byte, so the agent can still send. The message is registered through the same code path as the extension's, stamped as agent-sent.send, with the agent's own Gmail access
MailSignals does not send. No Gmail scope, no SMTP, no queue.
confirm the ids, or cancel
Confirming records the Gmail message and thread ids the agent's send returned, so the row in your Gmail gets its checkmark and a reply can be matched. Cancelling deletes a prepared message that was never sent; without it an abandoned preparation would sit in the dashboard forever as "sent, never opened".
The send time defaults to the moment of preparation, which is what the too-fast rules measure from, so the tool description tells agents to prepare immediately before sending. Prepare is not idempotent: the same body prepared twice is two messages with two pixels.
The credential
- An agent key is minted in the dashboard, looks like msk_ followed by 43 base64url characters (256 random bits), and is shown exactly once.
- The secret is never stored. Verification is one document read keyed by a salted, domain-separated hash, so a leaked database yields nothing usable.
- A key authorises the agent routes and nothing else: it cannot read your history, export it, change a setting, delete anything or mint another key. A separate middleware on a separate router enforces this, so a route added to the main API later cannot become reachable by accident.
- Revoking deletes the lookup document; the key stops on the next request. The listing keeps a tombstone so you can see the key existed. Messages it created are untouched.
- Up to 20 active keys per account. "Last used" is written at most every five minutes; it is a convenience, not an audit log.
Not mangling the message
The body must survive. Preparation returns the caller's original HTML, untracked, if the parser throws, if the parsed body loses the caller's words, if the pixel could not be added, or if signing fails. Losing tracking is always better than sending a mangled message: the server-side form of the extension's own rule.
Two guards, because they catch different failures: comparing the document's text before and after our own mutation catches damage we do; comparing against a tag-stripped reading of the raw input catches a parse that swallowed everything, which a document compared with itself cannot see. A bare fragment is wrapped before parsing because the parser yields an empty body for one, which would make an email of nothing but a pixel.
The MCP server
A stdio server exposing five tools: mailsignals_whoami, mailsignals_prepare_email, mailsignals_confirm_sent, mailsignals_cancel and mailsignals_status. It holds no policy: every tool is one HTTP call, and error codes are translated into a sentence naming the fix. It talks only to the address you configure.
The prepare tool's description tells agents not to use it for a draft the person will send from Gmail, because the extension already tracks those and preparing them here would put a second pixel in the message. Nothing enforces it; the description is the guard.
The dashboard's "Copy MCP config" button produces the block on the right. The package is private to the repository, so run it from a checkout unless you publish it yourself.
{
"mcpServers": {
"mailsignals": {
"command": "npx",
"args": ["-y", "@mailsignals/mcp"],
"env": {
"MAILSIGNALS_API": "https://app.yourdomain.com/api",
"MAILSIGNALS_AGENT_KEY": "msk_…"
}
}
}
}
What is deliberately not here
| Absent | Why |
|---|---|
| Sending | No Gmail scope, no SMTP, no queue. The agent sends. |
| Draft support | The extension already covers drafts. |
| Webhooks | An agent polls status if it wants to know. |
| Rate limiting | Matches the rest of the API. A leaked key can create messages until it is revoked, so revoke it. |
| A separate link pass | The agent path uses the shared preparation code; the extension still carries its own copy of the same pass, and the shared code is to prove itself server-side first. |
Every per-message route answers 404 for a message belonging to another account, never 403, for the same reason the rest of the API does. The whole path, including a real open and a real click through a signed link, is exercised by an end-to-end suite of 69 checks against a deployment.
Where it lives
shared/src/prepare.ts- Preparing a body, pure, with the guard that returns the caller's HTML on any doubt; 33 tests.
functions/src/api/agent.ts- The agent routes and the key middleware.
functions/src/api/agent-keys.ts- Minting, listing and revoking keys.
mcp/src/index.ts- The five tools.
scripts/e2e-agent.mjs- The end-to-end suite.
Related: the guide, with the wiring for an MCP client · agent keys in the security model.
Give your agent the same honesty you get.
Mint a key in Settings, paste the MCP config, and every message the agent sends shows up with its verdicts and reasons like any other.