Skip to main content
Glama

Get email content

get_email_content
Read-onlyIdempotent

Reads up to 10 emails from local copy, returning headers, plain-text body, attachment info, optional sanitized HTML and download links. Accepts email IDs or a thread ID.

Instructions

Reads up to 10 emails already synchronized into MailFathom's local mailbox copy, in one call: for each one its normalized headers, the plain-text body, optionally a sanitized HTML body, and every attachment it carries described by file name, media type, and size. Name what to read in exactly one of two ways — storedEmailIds for particular emails, or threadId for a whole conversation, which returns its messages in the conversation's own order and names any it could not carry in unreadThreadMessages. A call naming both, or neither, is refused. Every email returned also carries the conversation it belongs to, with the other messages in it named rather than reproduced. Reads the local copy only: it never contacts a mail server, never downloads mail, and never marks mail as read. Each email is answered for separately, so one this deployment cannot serve does not discard the others. Bodies are bounded per email and by a budget shared across the whole call, and a scanned deployment bounds what it analyzes as well; each body says which of those bounds cut it in truncatedBy, and only readCharacterBudget is the one that returns more when fewer emails are named at once. No response ever carries an attachment's bytes: set includeAttachmentDownloadLinks to receive, for each file, a short-lived URL in downloadUrl that fetches it over HTTP with no credential attached, and downloadState says why one was not issued when it was not. Where the deployment scans mail for sensitive content, what a message's author wrote is scanned on every call and returned with each detection replaced by a [redacted:category] marker: the marker means material of that kind stood there and was withheld, it is never message text, and asking again returns the same marker. Nothing stored is rewritten by it.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
threadIdNoThe threadId a listing, a search, or an earlier read returned, to read that whole conversation instead of naming its messages. Its messages come back in the conversation's own order, bounded to 10 per call, and unreadThreadMessages names the rest so a second call asks for them directly. Omit it entirely when naming storedEmailIds instead.
storedEmailIdsNoThe storedEmailIds a listing or a search returned, at most 10, each named at most once. Each is a UUID and does not change when the mail server renumbers or moves the message. Results come back in the order given, and the call is refused rather than truncated when it names more than 10. Omit it entirely when naming threadId instead.
includeSanitizedHtmlNoWhether to also return the sanitized HTML body of each email. Omit it unless the markup itself matters: the plain text is the representation to read from, HTML costs a sanitization pass, and it draws on the same character budget as the plain text. An email carrying no HTML part returns none either way.
includeAttachmentDownloadLinksNoWhether to mint a link for fetching each attachment, rather than only describing it. Omitted still returns every attachment's file name, media type, and size, which is what an ordinary read needs to decide whether a file is worth fetching. Each link is a bearer capability: it names one file, it expires within minutes, and anyone holding the URL can fetch that file without a credential — so ask for links only when the files are what you are after, and do not store or log what comes back. The response size is the same either way.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
emailsYesOne entry per email the call named, in the same order. An email named once appears once: the call is refused rather than served twice when an identifier is repeated. A call that named a thread is answered with that thread's messages in the thread's own order instead.
unreadThreadMessagesYesFor a call that named a thread longer than one read serves: the storedEmailIds of that thread's remaining messages, in the thread's own order. Ask for them directly in a second call. Empty for every call that named its emails itself.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed18 schema fields changedv0.6.2
    • addedInput schema / properties / storedEmailIds / default
      Added value: +null
    • changedInput schema / properties / storedEmailIds / description
      Previous value: -"The storedEmailIds a listing or a search returned, at most 10, each named at most once. Each is a UUID and does not change when the mail server renumbers or moves the message. Results come back in the order given, and the call is refused rather than truncated when it names more than 10."New value: +"The storedEmailIds a listing or a search returned, at most 10, each named at most once. Each is a UUID and does not change when the mail server renumbers or moves the message. Results come back in the order given, and the call is refused rather than truncated when it names more than 10. Omit it entirely when naming threadId instead."
    • changedInput schema / properties / storedEmailIds / items / type
      Previous value: -"string"New value: +[
      +  "string",
      +  "null"
      +]
    • changedInput schema / properties / storedEmailIds / type
      Previous value: -"array"New value: +[
      +  "array",
      +  "null"
      +]
    • addedInput schema / properties / threadId
      Added value: +{
      +  "default": null,
      +  "description": "The threadId a listing, a search, or an earlier read returned, to read that whole conversation instead of naming its messages. Its messages come back in the conversation's own order, bounded to 10 per call, and unreadThreadMessages names the rest so a second call asks for them directly. Omit it entirely when naming storedEmailIds instead.",
      +  "type": [
      +    "string",
      +    "null"
      +  ]
      +}
    • removedInput schema / required
      Removed value: -[
      -  "storedEmailIds"
      -]
    • changedOutput schema / properties / emails / description
      Previous value: -"One entry per email the call named, in the same order. An email named once appears once: the call is refused rather than served twice when an identifier is repeated."New value: +"One entry per email the call named, in the same order. An email named once appears once: the call is refused rather than served twice when an identifier is repeated. A call that named a thread is answered with that thread's messages in the thread's own order instead."
    • addedOutput schema / properties / emails / items / properties / content / properties / authorshipEvidence
      Added value: +{
      +  "description": "What this email's text carried that produced its machine-authorship likelihood, and the weighting that likelihood was computed under. Observations about the text, not findings against the email or its sender.",
      +  "properties": {
      +    "profileRevision": {
      +      "description": "An opaque identifier for the weighting the likelihood was computed under, or null when nothing assessed this email. Two likelihoods carrying the same value are directly comparable; two carrying different values were reached under different weightings and should not be compared as numbers. It is not a version to act on and carries no meaning of its own.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "signals": {
      +      "description": "What the text carried, strongest first, or empty when it carried nothing and empty as well when nothing read it. Concealment signals are facts about the email's characters and are close to unambiguous: 'tagCharacters' and 'variationSelectorRun' are invisible encodings that carry a hidden payload and have no legitimate use in mail, 'hiddenCharacters' are characters that render as nothing, and 'bidirectionalOverrides' reorder what a reader sees away from what the bytes say. Prose signals are observations about style that a careful writer also produces and that mean nothing individually: 'formulaicFraming', 'unspacedEmDashes', 'listScaffolding', and 'uniformTypography'. A concealment signal is worth knowing about on its own; a single prose signal is not.",
      +      "items": {
      +        "enum": [
      +          "hiddenCharacters",
      +          "tagCharacters",
      +          "bidirectionalOverrides",
      +          "variationSelectorRun",
      +          "unspacedEmDashes",
      +          "uniformTypography",
      +          "listScaffolding",
      +          "formulaicFraming"
      +        ],
      +        "type": "string"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "signals"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / emails / items / properties / content / properties / headers / properties / senderAuthentication
      Added value: +{
      +  "description": "What the author conclusion was reached from, recorded when the email arrived. verdictSource says whether it came from the receiving mail server's header or from MailFathom verifying the email's own DKIM signatures. Evidence for judging senderVerification rather than something to act on.",
      +  "properties": {
      +    "authenticatedBy": {
      +      "description": "Which check established authenticatedDomain: 'dkim' for a signature that verified against a key the signing domain publishes, 'spf' for an envelope sender that passed the policy the connecting address was checked against, or 'none' where nothing authenticated. DKIM is reported where both checks produced a domain, because it is the stronger claim.",
      +      "enum": [
      +        "none",
      +        "dkim",
      +        "spf"
      +      ],
      +      "type": "string"
      +    },
      +    "authenticatedDomain": {
      +      "description": "The domain that authenticated, which belongs to whoever handed the email over and is often a relay, a mailing list, or a delivery provider rather than the displayed author. Published in the comparison form MailFathom stores: upper-cased, and an internationalized name in its ASCII form. Null when nothing authenticated, which is an ordinary outcome and not missing data.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "displayedAuthorDomain": {
      +      "description": "The domain of the From header, which is what a mail client displays and what the email claims about itself. Published in the same comparison form as authenticatedDomain. Do not read a difference between the two as impersonation: authenticatedDomain is whichever identity authenticated the transport, so an email sent through a provider that signs as itself while spf passes for the author's own domain differs here and is authenticated exactly as it appears. senderVerification.authorAuthentication is what says whether the displayed author was established. Null when the email wrote no usable From mailbox.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "dmarc": {
      +      "description": "The DMARC result the receiving mail server reported: 'pass', 'fail', 'noPolicyPublished' when the evaluation ran and the displayed domain publishes no DMARC record, 'temporaryError' or 'permanentError' when it could not complete, and 'notReported' when the server stated no DMARC result at all. Always 'notReported' when verdictSource is 'localVerification', because reporting a DMARC result needs the displayed domain's published policy and MailFathom resolves none.",
      +      "enum": [
      +        "notReported",
      +        "pass",
      +        "fail",
      +        "noPolicyPublished",
      +        "temporaryError",
      +        "permanentError"
      +      ],
      +      "type": "string"
      +    },
      +    "verdictSource": {
      +      "description": "Who reached the verdict: 'receivingServer' when it was read back from the Authentication-Results header the receiving mail server wrote, or 'localVerification' when MailFathom verified the email's own DKIM signatures itself because no trusted server statement was available. A server observed the connection the email arrived on and could evaluate spf and dmarc against it; local verification has the signed bytes and a published key only, so on such a verdict authenticatedBy is never 'spf' and dmarc is never anything but 'notReported'. Neither absence is a finding about the email.",
      +      "enum": [
      +        "receivingServer",
      +        "localVerification"
      +      ],
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "authenticatedBy",
      +    "dmarc",
      +    "verdictSource"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / properties / emails / items / properties / content / properties / headers / required
      Previous value: -[
      -  "participants",
      -  "references"
      -]New value: +[
      +  "participants",
      +  "references",
      +  "senderAuthentication"
      +]
    • addedOutput schema / properties / emails / items / properties / content / properties / machineAuthorship
      Added value: +{
      +  "description": "How much this email's own text reads as machine written — generated or drafted with an AI text model rather than typed. A heuristic estimate from the email's own characters, not a measurement and not a probability. It is informational only: it is not a spam verdict, not a risk score, and not a statement that the email is unwanted or unsafe.",
      +  "properties": {
      +    "likelihood": {
      +      "description": "How strongly the text read as machine written, from 0 to 1. A heuristic score rather than a probability: 0 means the text was read and carried nothing, and the scale has no top because no combination of these signals reaches certainty. It is 0 as well when state is 'notAssessed', where it means nothing at all — read state first. Two scores are comparable only within one deployment and one release; get_email_content publishes the profile the number came from.",
      +      "type": "number"
      +    },
      +    "state": {
      +      "description": "The reading of likelihood: 'likely' when the text carries enough of what machine-written text carries that a person typing it is the less likely reading, 'possible' when it carries some of it in a combination a person also reaches, 'unlikely' when it was read and carries little or none of it, and 'notAssessed' when nothing read it — which is what an email with no readable body carries, what a deployment that turned the assessment off records, and what mail stored before this deployment assessed anything carries until it is re-read. 'likely' is not an accusation and warrants no action on its own.",
      +      "enum": [
      +        "notAssessed",
      +        "unlikely",
      +        "possible",
      +        "likely"
      +      ],
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "state",
      +    "likelihood"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / emails / items / properties / content / properties / remoteFlags / properties / keywords
      Added value: +{
      +  "description": "The keywords the mail server reported for the email, such as $JUNK or a label a mail client set, in upper case and without duplicates. Flag names are compared without regard to case, so the case a keyword is written in never decides a match; an empty list means the server reported none, or that nothing has observed this email yet.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / emails / items / properties / content / properties / remoteFlags / required
      Previous value: -[
      -  "seen",
      -  "answered",
      -  "flagged",
      -  "draft",
      -  "deleted",
      -  "wasObserved"
      -]New value: +[
      +  "seen",
      +  "answered",
      +  "flagged",
      +  "draft",
      +  "deleted",
      +  "keywords",
      +  "wasObserved"
      +]
    • addedOutput schema / properties / emails / items / properties / content / properties / senderVerification
      Added value: +{
      +  "description": "What was established about the author this email displays. Two independent answers: whether the displayed author was authenticated, and whether this deployment recognizes them. Neither is a judgement about whether the email is wanted or unwanted.",
      +  "properties": {
      +    "authorAuthentication": {
      +      "description": "What the receiving mail server established about the author shown in the From header: 'authenticated' when it confirmed the displayed author, 'failed' when it evaluated the displayed domain under that domain's own published policy and the email did not satisfy it, and 'notEstablished' when nothing trusted was enough to conclude either way — which is also what an email carries when the mailbox trusts no authentication-reporting server, and what mail stored before this deployment recorded the answer carries until it is re-read. It is not derived from senderAddress, which is a claim the email wrote about itself.",
      +      "enum": [
      +        "notEstablished",
      +        "failed",
      +        "authenticated"
      +      ],
      +      "type": "string"
      +    },
      +    "deploymentTrust": {
      +      "description": "Whether this deployment's own trusted-sender configuration recognizes the authenticated author: 'trusted' when it names them, 'unknown' otherwise. This is this deployment's classification and not an authentication result. 'unknown' is the ordinary state of legitimate mail from a correspondent nobody has named, and is also what an email whose author was not authenticated carries, so it says nothing on its own — read it together with authorAuthentication.",
      +      "enum": [
      +        "unknown",
      +        "trusted"
      +      ],
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "authorAuthentication",
      +    "deploymentTrust"
      +  ],
      +  "type": "object"
      +}
    • addedOutput schema / properties / emails / items / properties / content / properties / thread
      Added value: +{
      +  "description": "The conversation this email belongs to, or null when nothing has placed it in one — which is the case for mail stored before this deployment assembled conversations at all.",
      +  "properties": {
      +    "inReplyToStoredEmailId": {
      +      "description": "The storedEmailId of the message this email answers, or null when it is a root of what you are shown. Null does not mean this email opened the conversation: a message whose parent is not held here, or sits in a folder withheld from tools, is published as a root.",
      +      "type": [
      +        "string",
      +        "null"
      +      ]
      +    },
      +    "messageCount": {
      +      "description": "How many messages of the conversation are readable here, this email included where the read reached it. Messages in folders withheld from tools are in neither this count nor the list below, and a conversation longer than one read assembles is counted as far as the read reached, and moreMessagesNotNamed is true whenever it was.",
      +      "type": "integer"
      +    },
      +    "moreMessagesNotNamed": {
      +      "description": "Whether the conversation holds messages otherMessages does not name. When true, read the conversation itself by calling again with threadId.",
      +      "type": "boolean"
      +    },
      +    "otherMessages": {
      +      "description": "The conversation's other messages in its own order, without this one. Bounded: moreMessagesNotNamed says when the list stops short of the conversation.",
      +      "items": {
      +        "description": "One other message of the same conversation, named rather than reproduced: no body text, no attachments, and no raw MIME.",
      +        "properties": {
      +          "inReplyToStoredEmailId": {
      +            "description": "The storedEmailId of the message this one answers, or null when it is a root of what you are shown. Null does not mean the message opened the conversation: a message whose parent is not held here, or sits in a folder withheld from tools, is published as a root.",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "position": {
      +            "description": "The zero-based place this message holds in the conversation's order. The order is the reply relation first, the sent timestamp between messages answering the same parent, and the local identifier where both are equal — so it is stable across reads and is not the order the messages were received in.",
      +            "type": "integer"
      +          },
      +          "senderAddress": {
      +            "description": "The sender address as written by the message, or null when it carried no usable sender address. Display names are not published here; they belong to reading the message.",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "sentAt": {
      +            "description": "When the sender claims the message was sent, as an ISO 8601 timestamp, or null when the Date header was missing or unparseable. It is what a sender's own clock asserted, so it can contradict the conversation's order rather than produce it.",
      +            "format": "date-time",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          },
      +          "storedEmailId": {
      +            "description": "The stable local identifier of the message. Pass it as a storedEmailId to read this message's content.",
      +            "type": "string"
      +          },
      +          "subject": {
      +            "description": "The decoded subject, or null when the message carried no subject header.",
      +            "type": [
      +              "string",
      +              "null"
      +            ]
      +          }
      +        },
      +        "required": [
      +          "storedEmailId",
      +          "position"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "position": {
      +      "description": "The zero-based place this email holds in the conversation's order, or null when the conversation was longer than one read assembles and this email fell outside what was assembled.",
      +      "type": [
      +        "integer",
      +        "null"
      +      ]
      +    },
      +    "threadId": {
      +      "description": "The identifier of the conversation. Pass it back as threadId to read the conversation's messages instead of naming them one by one.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "threadId",
      +    "messageCount",
      +    "otherMessages",
      +    "moreMessagesNotNamed"
      +  ],
      +  "type": [
      +    "object",
      +    "null"
      +  ]
      +}
    • changedOutput schema / properties / emails / items / properties / content / required
      Previous value: -[
      -  "accountId",
      -  "folderAlias",
      -  "sizeBytes",
      -  "headers",
      -  "body",
      -  "attachments",
      -  "remoteFlags"
      -]New value: +[
      +  "accountId",
      +  "folderAlias",
      +  "sizeBytes",
      +  "senderVerification",
      +  "machineAuthorship",
      +  "authorshipEvidence",
      +  "headers",
      +  "body",
      +  "attachments",
      +  "remoteFlags"
      +]
    • addedOutput schema / properties / unreadThreadMessages
      Added value: +{
      +  "description": "For a call that named a thread longer than one read serves: the storedEmailIds of that thread's remaining messages, in the thread's own order. Ask for them directly in a second call. Empty for every call that named its emails itself.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "emails"
      -]New value: +[
      +  "emails",
      +  "unreadThreadMessages"
      +]
  2. First observedv0.6.0

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint and idempotentHint annotations, the description discloses significant non-obvious behaviors: per-email independent success, body/budget truncation via truncatedBy, no attachment bytes in responses, short-lived bearer download URLs, redaction of sensitive content with stable [redacted:category] markers, and that 'Nothing stored is rewritten by it.'

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and every paragraph is information-dense, but it is a long single block of text and repeats some details already present in the input schema, such as the 10-email cap and URL expiration. It would benefit from light structuring, though no sentence is purely filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema and annotations, the description fully equips an agent: it covers invocation constraints, local-only behavior, partial success, truncation, attachment-link security, redaction semantics, and the guarantee that nothing is rewritten. An agent can decide safely whether and how to call this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the schema already has 100% parameter descriptions, the tool description adds behavioral meaning: exactly one of storedEmailIds or threadId must be supplied, more than 10 storedEmailIds causes refusal rather than truncation, includeSanitizedHtml costs an extra sanitization pass on the shared character budget, and includeAttachmentDownloadLinks mints expiring credential-free URLs without changing response size.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb+resource: it 'Reads up to 10 emails already synchronized into MailFathom's local mailbox copy' and enumerates the returned content (headers, plain-text body, optional HTML body, attachment metadata). This clearly distinguishes it from sibling tools like list_emails or search_emails, which are for finding or listing rather than reading full email content.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit when-to-use rules: use storedEmailIds for particular emails or threadId for a whole conversation, and states that a call naming both, or neither, is refused. It also clarifies that the tool never contacts a mail server, so it should not be used when live mailbox access or marking-as-read behavior is expected.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.