Using the tools
MailFathom publishes twenty-one MCP tools, and together they are the whole surface: an agent can see which mailboxes exist, list mail, read one message, search, ask a question, mark and label a message, send a message, reply to or forward one it already holds, find out what became of a message it sent and stop one that has not left yet, write a message into your Drafts folder and edit, delete, or send that, and keep the deployment's own contact book — nothing else. This page is the user's view of that surface: what each tool answers, what every result carries, what the deliberate limits are, and how to read a failure. The full contracts — every argument, every field, every bound — live in MCP tools and the feature pages it links, and this page does not restate them.
Fourteen of the fifteen mailbox tools are always within the deployment's reach. ask_mail needs a chat model and an embedding
model configured and working, so a deployment that has neither does not offer it at all; its absence from a tool listing
is that deployment saying it cannot answer questions rather than something being broken. The three sending tools are
always offered to a caller granted them and refuse the call where the account behind the message has no sending
configuration, because whether a
particular mailbox can send is a question about that account rather than about the deployment.
Which of the twenty-one you are offered is a second question, and its answer is the grant on the credential you connected with. A tool that grant does not permit is absent from the listing, and calling it anyway is answered as though no such tool existed — nothing names the permission that was missing, so a shorter tool list than this page describes is a question for whoever configured the deployment: what a credential may do. A deployment that wrote no grant, which is the default, offers everything it has, and the six contact tools are part of that everything.
A grant is not the only reason your listing may be shorter than this page. Every tool belongs to exactly one kind — the mailbox, the flags, sending, drafts, answering, and the contact book — and a deployment may publish some of those kinds and not the rest, in which case no credential reaches the withheld ones whatever its grant says. A client may narrow its own session further still by naming the kinds it wants in a request header. Neither can turn anything on: both only ever offer less, so nothing you write in a request makes a tool this deployment withheld appear. What this endpoint publishes records what an operator chose and what a client may ask for.
The model behind every call
A mailbox tool call reads the local copy that synchronization maintains. Nothing in a request reaches a mail
server, so a call is fast, works while the server is unreachable, and reading mail can never mark it as read — locally
or remotely. That holds for the contact tools too, including the three that write: what they change is a table in
MailFathom's own database, and no mail and no mail server is touched by any of them. set_mail_flags and the three
sending tools are the calls
whose effects are meant to leave this deployment, and none of them reaches a server while you wait: the first writes the
change down and the next synchronization run carries it, and the others write the message down and a delivery pass
offers it to a mail server seconds later. The price
of that model is freshness, which is why every listing and every search carries folderFreshness: one entry per
folder in scope, stating when synchronization last committed progress there, or that it never has. An agent that reads
mail without reading that field will eventually present an empty folder as an empty mailbox.
Results are bounded by design. A listing serves at most 100 summaries per page, a search at most 50 ranked matches, a body at most the configured character bound, a search extract at most a few dozen words. The bounds are the deployment's privacy control on how much mail one call can draw out, so they are refused rather than stretched when a caller asks for more, and none of them is a client argument to widen.
list_accounts — which mailboxes exist
Takes no arguments and returns the mailboxes this deployment serves. Call it first: every other tool narrows by account,
and this is where the names to narrow with come from. Each entry carries two of them — the accountId an operator
configured and the displayName they gave it — and either one names the account in a later call, the display name
matched without regard to case. Quote the display name to a person; the identifier is what other results report and what
stays stable if the readable name is changed.
Each account also lists its folders with the same freshness statement a listing carries, and the result says whether synchronization is running at all. An account with no folders listed has never been synchronized, which is a different thing from a mailbox that holds nothing.
Nothing about how MailFathom reaches a mailbox is returned: no server, no port, no user name, no credential.
list_emails — the timeline
Returns a page of summaries, newest received first by default, filtered by any combination of account, folder, sender, recipient, subject fragment, received range, seen state, flagged state, a keyword, and attachment presence. Every argument is optional; a bare call reads every folder every served account maps and lets tools read — configuration is what says which folders those are, and a folder it does not name is one this deployment does not have.
A summary is enough to recognize a message — subject, sender, recipients, timestamps, size, attachment counts, remote
flags — and carries storedEmailId, the identifier a content read uses. It names its account both ways, as accountId
and accountDisplayName, so telling a person which mailbox a message came from needs no second call. Five fields prevent
common misreadings:
senderVerificationis what somebody else established, next to thesenderAddressthe message wrote about itself.authorAuthenticationis what your mail server concluded about the sender shown inFrom—authenticated,failed, ornotEstablished— anddeploymentTrustis whether your own trusted-sender configuration names that sender —trustedorunknown. Read the two together.authenticatedbesideunknownis ordinary mail from somebody you have never listed and says nothing against it;unknownon its own says only that this deployment does not recognize the sender, and it is what an email whose sender failed authentication carries too.machineAuthorshipis about the text rather than the sender:statesays how much the message's own wording reads as machine written —likely,possible,unlikely, ornotAssessed— andlikelihoodis the number behind it. It is a hint, not a verdict. It is a heuristic estimate rather than a measured probability, andlikelyis not a finding against the message or the person who sent it — plenty of honest mail is drafted with an AI these days. MailFathom never acts on it. What makes it worth having is the strongest thing it notices: a message carrying characters no mail client displays, which is how instructions aimed at your agent get hidden from you.notAssessedmeans nothing read the text — an empty body, the reading turned off, or mail stored before this release.attachmentscounts real attachments separately from inline images, so a message whose only payload is a logo in its signature does not read as one carrying a document.remoteFlagsreports what the mail server last said about the message, includingflagged— the star a mail client shows, whichisRemotelyFlaggedalso filters on — andkeywords, the labels a client or a server set beside the five standard flags.keywordnarrows a listing to one of them, matched without regard to case. ReadwasObservedbefore trusting any of it: an email nothing has looked at yet carries every flag unset and no keyword, which is not the same as a server reporting none.contentAvailabilitysays whether the raw content is stored locally, and why not when it is not, instead of failing a later read unexplained. A message larger than the configured size limit reports so here and will report the same on every later read; one that arrived while local storage stood at its ceiling reports that it is waiting for room, and a later synchronization run fetches it.
Paging is a cursor: pass nextCursor back unchanged, with the same filters and direction. A cursor is bound to the
filters that issued it, so changing a filter mid-walk is refused (52002) rather than answered with a page from a
different question.
Junk is left out unless you ask for it. Mail your provider or your own filter already set aside is not what a
timeline is for, and an agent reading it cannot tell mail written to deceive it from correspondence. Set
includeJunkMail: true when you are looking for a message a filter took; the result says which of the two listings you
got, in includedJunkMail. The same argument and the same field are on search_emails.
search_emails — ranked text search
Takes queryText — the words to find — plus the same structured filters a listing has, and returns one window of
matches ranked by relevance, each carrying the summary a listing would show and short extracts of the body around what
matched, the matched runs wrapped in **.
What it searches is the lexical index: subject, normalized participant addresses, and the extracted body text. Text
inside attachment payloads is not indexed at all, and encrypted mail has no indexed body either. subjectFragment and
queryText are different arguments doing different work: the fragment narrows which emails are eligible, the query
text is what the eligible ones are ranked against.
Word the query in the language the mail was written in. The index matches words rather than translating them, so a query written in the language you asked your question in reaches mail written in that language and in no other. A mailbox holding several languages is searched once per language a question could plausibly be answered in.
Read retrievalMode on the result to know how the match was made. lexical means by words rather than by meaning,
so a query term that appears nowhere in a message will not find it. hybrid means the same ranking was combined with a
search by embedding similarity, so mail whose meaning is close is found too — a search for a roof leak reaching the
message that said water damage. Which one you get depends on whether the server has an embedding provider configured and
reachable, and it can differ between two calls, which is why the field is on every response rather than something to
look up once.
semanticSearch beside it says why a lexical answer was lexical. inactive means the server does not embed mail
at all, so lexical is what it is meant to do and nothing is wrong. available means it does and its provider is
answering. degraded means it does but currently cannot reach the provider or its configuration is wrong, so these
results are narrower than the server intends — retrying will not help, and it is the server's operator who has a
credential or a declaration to fix. Recovery is automatic once they do.
There is deliberately no search cursor. Relevance order moves as mail keeps arriving and indexing catches up, so a second page could silently skip or repeat matches; ask a narrower question instead of a longer window.
get_email_content — up to ten messages
Takes the storedEmailId values a listing or search returned — at most ten, each named once — and returns, for each
one, normalized headers, the plain-text body, optionally a sanitized HTML body, and every attachment it carries by name,
type, and size. Set includeAttachmentDownloadLinks to also receive, for each file, a short-lived URL that fetches
it over ordinary HTTP. No response ever carries a file's bytes, so a message with a video in it costs the same as one
with a note.
Reading the top few results of a search is therefore one call rather than one per message. The entries come back in the
order you named them, and each carries either content or a failure saying why there is none, so one message this
deployment cannot serve does not discard the others.
Or name a whole conversation instead. Pass threadId — the value a listing, a search, or an earlier read returned —
and leave storedEmailIds out entirely: the conversation's messages come back in its own order, still at most ten, and
unreadThreadMessages names the ones that did not fit so a second call asks for them directly. Give exactly one of the
two; a call carrying both, or neither, is refused rather than guessed at.
Ten parts of the result exist so that an agent does not misreport a message:
- The sender verdict comes with the evidence behind it.
senderVerificationis the same pair a listing carries, andheaders.senderAuthenticationadds what it was reached from: the domain that actually authenticated, the domain theFromheader displayed, which check established the first (dkim,spf, ornone), the DMARC result your server reported, andverdictSource, which says whether that server reached the verdict or MailFathom verified the signatures itself because no server verdict was found — on the second, DMARC is alwaysnotReportedand no SPF is attempted, so neither absence says anything about the message. The two domains differing is ordinary rather than a warning: the authenticated one is whichever identity authenticated the transport, so mail sent through a provider that signs as itself differs there and is authenticated exactly as it appears.authorAuthenticationis the answer to whether the displayed author was established. Either domain can benull, which means nothing authenticated or the message wrote no usableFrom— an outcome rather than missing data. - The authorship reading comes with what it was read from.
machineAuthorshipis the same band and number a listing carries, andauthorshipEvidenceaddssignals— what the text actually carried — andprofileRevision, which says which weighting produced the number, so two messages are only comparable when it matches. The signals are worth reading in two halves.tagCharacters,variationSelectorRun,hiddenCharacters, andbidirectionalOverridesmean the message contains characters your mail client never shows you, which is worth a look on its own;formulaicFraming,unspacedEmDashes,listScaffolding, anduniformTypographyare style habits any careful writer also has, and none of them means anything by itself. Both lists are empty on a message nothing assessed. - Truncation is explicit, and says which limit cut. Each body representation carries
truncatedByand the original character count, so a cut message is never summarized as a whole one.bodyCharacterLimitmeans the message is longer than any single call returns;readCharacterBudgetmeans the messages named before it used up the call's shared budget, and naming fewer at once returns more of this one;sensitiveContentScanCeilingmeans the deployment scans mail for sensitive content and analyzed as much of this body as it may, so the rest is withheld from every call rather than served unscanned. - An absent body has a reason.
availabilitydistinguishes a message that displayed nothing from one MailFathom cannot decrypt, from one whose content the size limit deliberately kept out of storage, and from one that is simply waiting for storage room — which is the one case where asking again later returns the body. - Attachments are described always, fetched through a link. Every read tells you what each file is called, what it
is, and how large it is, because that is what you decide against when choosing whether to fetch one;
attachmentsis[]only when the message carries none.attachmentCountssays how many either way. - A link is a secret with a deadline.
downloadStateisnotRequestedwhen you did not setincludeAttachmentDownloadLinks,issuedwhendownloadUrlfetches the whole file untildownloadExpiresAt, andunavailablewhen this server issues no attachment links at all — which only its operator can change. Anyone holding the URL can fetch that file without a credential, so fetch it once and do not log or store it; ten minutes is the usual window and half an hour the most any server may allow. When it expires, callget_email_contentagain for a new one. - File names are sanitized. An attachment name is untrusted text from the message; what is published is a bare, normalized name, with a flag saying whether it had to be rewritten.
- A too-long or repetitive list is refused, not trimmed. More than ten identifiers, none at all, or the same one
twice ends the call with a code rather than returning part of what you asked for — and so does a call that names both
storedEmailIdsandthreadId, or neither. threadsays what else is in the exchange, without returning it. Every message comes back with the conversation it belongs to: its identifier, where this message sits in it, which message it answers, how many messages of it you may see, and the others named by subject, sender, and sent time. Nothing of their bodies travels with it, so opening one is still a call. Conversations are assembled from the message identifiers mail carries and never from subjects, so a reply that renamed the subject stays in the exchange and two unrelated messages sharing one never join.threadis absent for a message this server has not assembled a conversation for yet.- A
[redacted:…]marker is not message text. On a server whose operator switched sensitive-content scanning on, the body, the subject, and the display names come back with each detected credential or piece of personal data replaced by[redacted:<category>]. Report it as material of that kind withheld rather than quoting it as words the sender wrote, and expect the same marker every time. Only the first 40 named participants of a message have their display name scanned; every one after that is published as an address with no name, so on such a server a missingdisplayNamedoes not prove the sender wrote none. Sensitive-content scanning records what is scanned.
The HTML body, when requested, is aggressively sanitized — no scripts, no styles, no remote loads — and email content records exactly what survives.
set_mail_flags — marking, starring, and labelling
The one tool that changes your mailbox rather than MailFathom's copy of it. It marks a message read or unread, stars or unstars it, and adds, removes, or replaces its keywords — the labels your mail client shows as tags. All three in one call, because triaging a message is one decision, and each of them optional: a call that names none is refused rather than treated as a change of nothing.
It does not happen while you wait. The call writes the change down and answers immediately; the account's next
synchronization run is what tells your mail server. So the answer reports records rather than a mailbox that has already
changed. A star that has not appeared in your own client after a few minutes is followed up by making the same call
again with the same requestId: it answers with the same records and the lifecycle each has reached by then, rather
than starring the message a second time. What you get from that arrangement is that a crash mid-flight leaves a
change that finishes by itself instead of a value MailFathom thinks it set and the server never heard about.
Keywords have three directions and the third is the one to be careful with. add and remove touch only the keywords
you name. replace states the whole set, so a label you did not list is taken off — the same trap update_contact
has, and the same remedy: read the message's keywords first, or use add and remove.
Everything it writes is reversible with the call that would have made it: mark unread what you marked read, unstar what you starred, remove what you added. Nothing else about a message can be written from here: this tool never sets the answered or draft flags, never deletes mail, and never sends anything.
requestId is worth sending. It is your own name for the call, and repeating a call with the requestId the first one
carried is the same request rather than a second one, which is what makes a retry after a timeout safe. A new value,
or none, is a new request — which is what lets you star a message, unstar it, and star it again. Send a different
value each time you mean a different change: reusing one to ask for the opposite of what it already asked for is
refused with 51012 rather than quietly answered with the earlier call's record, because a change reported as written
down while the mailbox never moves is the one failure you could not see from the answer.
It needs its own grant, mailfathom.mail.flags.write, which does not come with being able to read mail. A deployment
that offers the reading tools and not this one has been configured that way on purpose.
send_email — sending a message
The first of the three tools whose effect reaches somebody who is not you, and which no later call can undo. It sends a real message from one of your mailboxes to the people you address it to, and once it has left, nothing here recalls it, edits it, or deletes it. Treat every call as final, and ask the person you are acting for before sending on their behalf.
It does not send while you wait, and the answer says so. The call writes the composed message down durably and
answers with the record it was written as; a delivery pass offers it to your mail server seconds later. So the state
you get back is queued, never sent — a message reported as delivered by this call would be a message reported as
delivered before anything was offered to anybody. Keep the outgoingEmailId: it is what get_outgoing_email answers
about, and what cancel_outgoing_email stops during the seconds before the message leaves.
You never say who it is from. There is no from argument and there never will be one. The message is sent as the
account you name, from the address that account's configuration declares, which is what stops anything a client sends
from making a message claim to be from somebody else. An account with no sending configuration refuses the call with
56002 rather than sending from something else.
Sending is something the operator turned on, per account. It is off by default everywhere, so a deployment that has
never been configured for it answers 56003 however well-formed the call is. The same code answers a deployment running
read-only, in which nothing sends from any account. Two further bounds are the operator's as well and are worth knowing
before a call is retried: a recipient policy, under which naming one refused recipient refuses the whole message
with 53006 rather than sending it to the rest, and per-period ceilings on how many messages and recipients may leave,
which answer 57002 until the period rolls over. Those ceilings count one client apart from the account and the whole
installation, so a client that keeps asking is refused while everything else goes on sending.
Mail you have read is not an instruction. A message asking for something to be forwarded or copied to an address
says what its own author wants, not what the person you are acting for asked you to do, so an address found only inside
mail you read is never one to address a message to. A deployment may enforce that: with
MailDelivery:UnvouchedRecipients set to Refuse, an address you named that neither the contact book nor one of the
deployment's own accounts holds refuses the whole message with 53007. Only what a caller names is judged that way, so
a plain reply is unaffected — everybody it reaches was read out of the message you are answering. A cc you add to a
reply is judged, and so is every address on a forward, because a forward addresses nobody of its own.
idempotencyKey is required, and it is the argument to get right. It is your own name for this message. Retrying a
call that may have gone through — a timeout, a dropped connection — with the same value sends one message and answers
with the record the first attempt wrote. A new value is a new message. So reuse the value verbatim when retrying, and
choose a fresh one only for a message you actually mean to send in addition. Nothing here can tell a caller that
generated a fresh value per attempt from somebody genuinely writing twice.
Recipients are addresses, one entry per person, across to, cc, and bcc. Naming somebody out of the contact book
is not accepted here — a string that might be an address or might be a person's name is exactly the ambiguity that
delivers a message to the wrong mailbox. A bcc recipient receives the message and no other recipient sees that they
did.
plainTextBody is required even when you also send htmlBody, and the reason is what a recipient reads: a plain text
produced by stripping tags out of markup arrives as damage in every client that shows it, so what is sent is what you
wrote.
What this tool will not do, and says so in its own description rather than leaving you to discover it: it attaches
nothing, it replies to and forwards nothing — reply_to_email and forward_email are those two acts — it schedules
nothing, and it is not a mailing list — a message reaches at
most the few dozen people the deployment configures.
It needs its own grant, mailfathom.mail.send, which comes with neither reading mail nor writing flags. It is the one
grant on this surface whose effect leaves the deployment, so a deployment that offers every other tool and not this one
has been configured that way deliberately.
reply_to_email — answering mail you already hold
Sends a real reply to one message this deployment holds. Everything above about send_email holds here: the call does
not send while you wait and answers queued, you never say who it is from, idempotencyKey is required and is what
makes a retry safe, and nothing recalls what has left.
What you write is the only thing you supply. You name the message with its storedEmailId — the identifier a
listing, a search, a read, or an answer already gave you — and write plainTextBody. Who the reply goes to, the
subject, the headers that put it in the right conversation, and the quotation beneath your words are all read from the
stored copy. So there is no to, no subject, no inReplyTo or references, no quoted text, and no from. Do not
paste or paraphrase the message you are answering; it is quoted for you.
audience is required and there is no default. senderOnly answers whoever asked for answers and nobody else;
everyone also answers everybody the original named in To and Cc. Getting it wrong publishes a private answer to a
room, or drops the rest of a conversation — and no later call fixes either, which is why it is two names you must
choose between rather than a flag that quietly defaults.
cc is the only way to widen it, and it adds to whoever the audience already reaches. There is no way to re-address a
reply and no way to add a hidden recipient.
forward_email — passing one on
Sends a real message this deployment holds on to people the original never named, with the files it carried. to is
required, because a forward addresses nobody on its own; cc and bcc behave as they do on send_email. The subject,
the forwarded message beneath what you write, and the attachments are read from the stored copy — there is no
attachment argument anywhere on this surface.
It passes on somebody else's correspondence, so it is the call to be most deliberate about: ask before forwarding somebody's mail. A message carrying more or larger files than the deployment sends is refused naming the limit rather than forwarded without them.
What both of them refuse
Both need mailfathom.mail.send and mailfathom.mail.read — an answer is derived from the message it answers, and
neither grant implies the other. A credential holding only the sending grant is offered both tools and refused by the
call, so a deployment meant to let an agent reply grants both names.
A message you cannot answer is one answer, whatever the reason. An identifier nothing is held under, mail of an
account this deployment no longer serves, mail in a folder the operator withheld from tools, and a message whose stored
content is no longer readable all come back as 53005, worded identically. Nothing tells you which — otherwise
replying to identifiers one at a time would map out what the deployment holds and what was withheld from you.
get_outgoing_email and cancel_outgoing_email — what became of a send, and stopping one
A sending tool answers queued and an identifier, which is honest and leaves you holding a question. These two answer
it. Call get_outgoing_email instead of sending again when you are unsure whether a message went out — a second
send is a second message in somebody's mailbox, and that is exactly the mistake the pair exists to prevent.
get_outgoing_email takes the outgoingEmailId the send answered with and reports where the message stands: the
state, how many delivery attempts it has taken, what a mail server has said about each person you addressed it to
including the reply code, and the five-digit code the last attempt failed on if one has. It reads a record this
deployment already holds and contacts no mail server, so the answer is as fresh as the last delivery pass rather than a
live check. Nothing about the message itself comes back — no subject, no body, no attachments — because you already have
what you sent.
cancel_outgoing_email takes the same identifier and stops the message while it is still waiting. It cannot recall a
message that has already been transmitted. The window is between the send being written down and the first byte going
out — ordinarily seconds — and past it the call is refused with 58001 and nothing is withdrawn. The message is
destroyed rather than paused: nothing reschedules it, no later call brings it back, and sending it after all means
queueing it again with a fresh idempotencyKey. Cancelling something already cancelled succeeds and does nothing
twice, so repeating the call after a timeout is safe.
There is no listing of what a mailbox has sent. Both tools answer about one identifier at a time and nothing on this
surface enumerates outgoing mail, which is why keeping the identifier a send answered with is worth doing — an
identifier you did not keep is a send nothing on this surface reaches. The view of the whole outbox belongs to whoever
runs the deployment, through mfctl outbox, and is reached with an administrative credential rather than with yours.
You can only reach what you queued. A message some other credential sent reads as not found, in exactly the same
words as an identifier nothing is held under — 53007 for both, so an identifier alone never establishes that this
mailbox sent something. Both take mailfathom.mail.send, the sending grant rather than the reading one: what this
mailbox has written to whom is not something a credential given the mailbox to read is thereby told.
The draft tools — writing a message and leaving it for you
Four tools cover a message that is written and not sent. save_draft writes one into your own Drafts folder,
update_draft replaces what it holds, delete_draft gives it up, and send_draft sends it. Only the last of the
four causes mail to leave, and it is the only one behind the sending grant — which is the whole reason the surface is
shaped this way: a deployment can let an agent prepare mail without letting it send any, and the worst a mistake costs
you is a message in Drafts that you delete.
save_draft takes one of two shapes and refuses a call that states both or neither. A message of its own names
account and subject and is addressed with to, cc, and bcc. An answer to mail this deployment already holds
names answeredEmailId and answering — senderOnly, everyone, or forward — and names neither account nor
subject, because the account, the subject, the threading headers, the quotation, and a forward's attachments are read
from the stored message exactly as reply_to_email and forward_email read them.
A draft addressed to nobody is an ordinary draft. Writing the message before deciding who reads it is what drafting
is for, so no recipient is required; send_draft is where the absence is refused, and the remedy is update_draft
rather than saving again.
These are the only calls here that wait on your mail server, and for one round trip: the draft is written down
first and the copy is then put into your Drafts folder, or taken back out of it, while whoever asked is still there.
So a saved draft comes back saying filed when your folder shows it and held when it does not — because the server
could not be reached, or because the account maps no folder to the drafts role, in which case it never will and the
draft lives here alone. Either way the draft is yours to edit, delete, and send.
There is no idempotencyKey, and calling save_draft twice writes two drafts. That is deliberate: a duplicate
draft costs you a deletion, where a duplicate send costs a recipient a second message. So a retry after a timeout
leaves a second draft to remove rather than a message sent twice, and the way to change a draft is update_draft with
the draftId the first call answered.
update_draft states the whole message rather than the part that changed. A recipient you leave out is no longer
addressed, a body you do not restate is gone, and an htmlBody you omit is dropped — so send everything again, not
only the edit. The draft keeps its draftId, its revision goes up by one, and your folder ends up showing one
message rather than one per edit.
Only a draft MailFathom wrote can be reached. A message you drafted in your own mail client is held under no
identifier of this deployment's, so nothing here names it, edits it, or deletes it. Every way of naming a draft that
cannot be acted on — one nothing is held under, one already deleted, one already sent, another account's, or text that
is no identifier at all — comes back as 53008, worded identically.
send_draft is a real send and behaves like one. Everything under send_email holds: the call does not send while
you wait and answers queued with an outgoingEmailId, you never say who it is from, nothing recalls what has left,
and get_outgoing_email and cancel_outgoing_email are what you read and stop it with. What differs is that it takes
no message at all — the message, the recipients, and the account are the draft's, so edit the draft and read it back
before sending. There is no idempotencyKey here either, for the opposite reason: the draft is the identity, so
promoting one draft sends one message however many times the call is made.
Everything the deployment refuses a send for is asked at the moment you send rather than when the draft was written, so
a draft written before an operator tightened a recipient policy or a ceiling is refused by the rule that holds today —
and a refused promotion leaves the draft exactly as it was. The draft is not deleted when send_draft answers either:
the message is queued rather than sent, so it stands in your folder until the message has actually been delivered.
save_draft, update_draft, and delete_draft need mailfathom.mail.drafts.write, and a draft that answers stored
mail needs mailfathom.mail.read beside it. send_draft needs mailfathom.mail.send, the same grant every other act
that causes mail to leave is admitted by. A credential granted drafting and not sending is not offered send_draft at
all, and a call naming it is answered as a call naming a tool that does not exist.
ask_mail — a question, answered with its sources
Takes a question in ordinary words, optionally narrowed to accounts and folders, and returns prose plus the emails the answer was drawn from. A chat model conducts the run and looks up mail as it decides it needs context, so this is the one tool that costs a provider call and takes seconds rather than milliseconds. Ask it when the answer spans several messages; search when the messages themselves are what you want.
The question is yours to write, not a query to construct: its words are not matched against the mail, and the lookups are
the model's own. That is also why there is no sender or date filter here — one supplied would narrow every lookup without
the model knowing why its searches came back empty. The model narrows its own lookups instead, by the same sender,
recipient, subject, date, seen state, and attachment filters search_emails publishes, so a question about one person's
mail or one week reaches that mail rather than competing for it in a ranking. What it can and cannot ask for is
Mail answering § What one lookup may ask for.
Ask in whatever language you like. The answer comes back in the language of the question, whatever language the mail behind it was written in, and what it quotes from a message — a subject, a name, the phrase a claim rests on — stays as that message wrote it so the citation can still be checked. The lookups are worded the other way round, in the languages the mailbox plausibly holds, which is why a Polish question reaches English mail here and a search worded the same way would not. Mail answering § A question in one language, mail in another records both.
Junk is outside every lookup a run makes, and here there is no way to ask for it. The answer is written by a model
from the mail it retrieved, so a message written to deceive a reader would arrive as ordinary correspondence with
nothing left to notice it. Use search_emails with includeJunkMail when junk is what you are looking for.
What leaves your instance when you ask
Asking is the one thing MailFathom does that sends your mail somewhere else on demand, so what leaves is worth knowing before the tool is enabled at all. Exactly two things reach the chat endpoint the operator declared:
- your question, as you wrote it, and
- the extracts the run retrieved — for each message the model looked up: the extract itself, the stable identifier, the account and folder alias, the subject, and the received time.
Beside them travels the run's own instruction, which is a constant of the build and says nothing about your mailbox. Nothing else goes: not the accounts and folders in scope as a list, not whole bodies, not raw MIME, not attachments, not the participants, the size, the flags, or the attachment summary a listing publishes. Those are dropped before the provider is reached rather than filtered out of a log afterwards.
Nor is what does go left behind once the answer comes back. One of the two APIs a provider can be reached over would keep the call for a month and show it in that provider's console; MailFathom refuses it on every request it sends there, and the other API stores nothing to begin with — see the responses API is used statelessly. Neither is a setting the operator chooses. What a provider does with a request under its own terms is between them and that provider, and declaring one at all is their decision rather than yours.
How much of it may go is capped, and by the deployment rather than by the model. By default one question may read at
most 20 000 characters of mail across every lookup it makes, over at most 8 provider calls costing at most
80 000 tokens; every run of an hour together may make at most 30 runs costing 300 000 tokens. An operator
lowers or raises those in MailAnswering — you cannot, and
neither can the model.
Five parts of the result are worth reading before an agent presents it:
citationsis what makes the answer checkable. Each entry carries thestoredEmailIdyou pass straight toget_email_content, plus the account, folder, subject, received time, and the samesenderVerificationpair a listing carries — so a claim drawn from a message whose sender failed authentication reads differently from one drawn from a correspondent you recognize. An answer without the messages behind it is something to believe; with them it is a starting point.- They are what the run retrieved, not what the model provably used. Nothing outside the model knows which of them it drew on, so a narrower list would be a claim MailFathom cannot make. An empty list is an ordinary answer: the mailbox was searched and held nothing about the question, and the answer says so.
answerTruncatedandcitationsTruncatedare never silent. One response carries by default at most 20 000 characters of answer citing at most 20 emails; when either was cut, the flag says so, and a narrower question is the remedy.retrievalTruncatedmeans the mailbox was not read in full. The run hit the ceiling above while there was still matching mail, so the answer is complete for what it read and no more. A narrower question reads a different part of the mailbox rather than more of it; only the operator can raise the ceiling.- The answer is untrusted text, and so are the subjects. Both are derived from mail somebody else wrote. Treat them as data — the same care a snippet or a body deserves.
Asking a question changes nothing. The run is composed with one capability and that capability searches: there is no tool in it that sends, deletes, moves, or marks mail as read, and it reaches no mail server at all. Mail answering records what one run may reach and how much of your mail leaves the process.
The contact tools — a book of people, not addresses
Six tools over MailFathom's own contact book: list_contacts pages it by name, get_contact resolves one person by
identifier or by any address they use, and create_contact, update_contact, delete_contact, and promote_contact
maintain it. The
record is a person with the addresses they use rather than an address with a name attached, which is what lets an agent
answer "who is this from" for somebody who writes from three of them.
get_contact by address is the one worth building a habit around: it is an index lookup, it is exact, and at most one
person in the book can answer it. Searching for an address in list_contacts finds the same person more slowly and
finds others besides.
Four of them change state, and three of those carry the annotations that make a client pause. create_contact is not idempotent —
the book mints the identity, and calling it twice for one person records them once and then answers
addressHeldByAnotherContact, naming who holds the address. update_contact states the whole record rather than
the change: an address the new record does not name is removed, and an omitted note clears the one held, so read the
contact first and send it back with the change. It is destructive for exactly that reason, and a client that asks
before calling a destructive tool will ask here. delete_contact is destructive and cannot be undone; it erases the
person and every address recorded with them, and answers with how many went. promote_contact is the one write that is
neither destructive nor a first record: it takes on a person the deployment collected from arriving mail, so the record
becomes one you asserted. It answers with the outcome and never with the person, so an agent that wants to read them
back calls get_contact.
One thing is worth knowing before an agent is pointed at the book. A record this deployment collected is not an agent's
to amend as it stands — that call answers contactWasCollected — and promote_contact is what it calls instead, after
which every other tool works on the record. And the book is somebody's list of real people: a name, an address, and
above all a note are things about a third party rather than facts about your mail, so what an agent writes there is what
you asked it to write down.
Reading a failure
An expected failure comes back as a tool error in one shape — a stable five-digit code and one safe sentence:
MailFathom error 53001: Mail account 'shared-billing' is not accessible.
The codes a user meets in practice:
| Code | What it means | What to do |
|---|---|---|
51001 / 51003 |
A page size or result limit outside the served range | Stay within 1–100 pages, 1–50 search results |
51002 |
A filter value the query does not accept — too long, malformed, or a range that ends before it starts | Fix the argument; the message names the filter and its limit, never the value |
51004 |
A storedEmailIds entry, or the storedEmailId a flag change, a reply, or a forward names, is no identifier this system issues — blank, truncated, or invented |
Pass the identifiers a listing or search actually returned; never construct or guess one |
51005 |
A content read named no messages, or more than the ten one call serves | Split the list into calls of at most ten |
51006 |
A content read named the same message twice | Remove the repeat; results are not served twice |
51007 |
A content read named both storedEmailIds and threadId, or neither |
Name exactly one of them; which you meant is not something the server guesses |
51008 |
A threadId is no identifier this system issues — blank, truncated, or invented |
Pass the threadId a listing, a search, or a read actually returned |
51009 |
A contact listing's page size, origin, or search text is not one the book serves | Stay within 1–200 contacts, name asserted or collected, keep the search to at most 320 characters |
51010 |
A contact was named with text that is no identifier and no usable address | Pass a contactId a listing or a write returned, or an address on its own — and exactly one of the two |
51011 |
A contact record breaks a rule the book holds | The message names the rule: a missing name, no address, a preferred address the record does not name, a value over its limit |
51012 |
A flag change asked for nothing, stated half a keyword change, named a keyword no mail server would keep, carried a requestId that is no identifier, or reused one that already asked for a different value |
Name at least one of seen, flagged, and the keyword pair; send keywordChange and keywords together; keep each keyword to one word of plain ASCII and name at most 64; keep requestId to at most 128 printable characters, or leave it out and let the server issue one; send a new one when you mean a new change |
51013 |
A field of a message you authored carries a value no message can be composed from — a line break in a subject or a recipient, an address outside ASCII, text naming no account, or an account or idempotencyKey that is blank, too long, or not printable |
Fix the field the message names; it names the field and never the value you sent |
51014 |
A message you authored is larger than this deployment composes — more people than the recipient bound allows, a body over its character bound, or a forward whose original carries more files or larger ones than this deployment sends | The message names the bound and the configured number: send to fewer people, send a shorter body, or accept that the message cannot be forwarded from here |
51016 |
The outgoingEmailId you passed is no identifier this system issues — blank, truncated, or invented |
Pass the value a sending tool answered with, verbatim |
52001 / 52002 |
A cursor this system did not issue, or one reused after the filters changed | Restart the walk from the first page |
52003 |
A contact listing's cursor is not one this system issued | Restart the walk; changing the search or the origin mid-walk is allowed and is not what caused it |
53001 |
The named account is not served here | Call list_accounts and use an accountId or displayName it returns |
53002 |
No such email in the local copy | The identifier is stale, or the mail was removed; list again |
53003 |
A folder was named by a role no folder in scope carries | Name the folder's alias instead, or ask the operator to map the role on that account |
53005 |
There is no email here you can reply to or forward under that identifier | One answer for four situations, deliberately: no such identifier, an account no longer served, a folder withheld from tools, or content no longer readable. Nothing tells you which — list again, and ask the operator if the message is one you expected to be able to answer |
53006 |
A recipient is one this deployment may not write to | The whole message was refused rather than sent to the rest; write to somebody the policy admits, or ask the operator to widen it. The answer never repeats the address |
53007 |
There is no queued message you sent under that identifier | One answer for two situations, deliberately: nothing is held under it, or somebody else queued it. Pass the identifier the sending tool answered with, and note that another credential's sends are never yours to read or stop |
55001 |
The email exists but its stored content is currently unreadable; a repair has been queued | Retry later — this is a local-consistency state, not a mail-server problem |
56001 |
This deployment cannot answer questions about mail, either at all or for now | Nothing about the question caused it; the message says which, and only the operator can change it |
56002 |
This deployment cannot send as the account you named | The account is served and readable, and sending from it is the part nobody configured; only the operator can add its delivery configuration |
56003 |
Sending is not turned on for the account you named, or this deployment is running read-only | The message says which; both are the operator's switch, and nothing you rewrite reaches an answer |
57001 |
Answering would cost more than this deployment allows | The message says which ceiling: a spent period is worth asking again once it turns over, while a question that reached what one question may cost needs to be narrower |
57002 |
Sending would carry this period past a ceiling the operator configured | The message names which ceiling; the period rolling over is when the same send can be asked for again |
58001 |
The message can no longer be cancelled | It is being transmitted, has been transmitted, or was already given up on. Nothing was withdrawn; call get_outgoing_email to see which, and treat a message that has left as final |
54001 |
Something failed for a reason the boundary deliberately does not describe | The server log has the detail, correlated by the request's trace |
53002 and 55001 also reach you inside a successful get_email_content result, as the failure on the entry for
the message they concern, because a content read answers for each message it was given rather than failing whole.
Refusals are deliberately uninformative in one direction: an account that does not exist and an account that is not
yours are the same answer, so the tool surface cannot be used to discover what a deployment serves. 53005 is the same
rule over mail rather than accounts, and it collapses four situations instead of two. A contact tool your
credential was not granted follows the same rule from the other end — it is missing from the tool listing, and calling
it anyway is answered as an unknown tool rather than as a permission you lack.
What the deployment sees, and what it does not
Every call is logged with the tool name, the outcome, and the duration — never with a filter value, a query text, a subject, or any part of a result. What you search your own mail for stays out of the operator's log by contract, and what the endpoint records is the precise statement. The flip side is worth stating plainly: snippets and bodies returned to an agent are mail content and travel to wherever that agent's model runs. Which model sees your mail is decided by the client you connect, not by MailFathom.
ask_mail adds a second destination, and it is one the deployment chose rather than the client: extracts of the mail a
run retrieves go to the chat endpoint its operator declared, bounded as § What leaves your instance when you
ask states. Nothing of the question, the answer, the query the model wrote, or
a retrieved passage reaches a log on the way — what the operator can read is how many runs and tokens a period spent,
which are counts and nothing about what they were spent on.
Expectations worth setting
- Freshness is the synchronization interval — five minutes by default, per account, plus the length of the run itself. Mail sent a moment ago is not yet listable.
- The remote
\Seenflag is an observation, not an effect. Results report the flags the last synchronization run saw, withwasObservedsaying whether any run has looked; reading through MailFathom never changes them. A change you asked for withset_mail_flagsshows up in those fields once the next run has both issued it and read the folder back, so a listing taken immediately afterwards still reports the value the server was last seen to hold. - Removing an account from configuration makes its stored mail unreachable through the tools, though the rows remain until removed. Disabling synchronization does not — the copy already stored stays readable.
- What happens to locally stored mail the server deleted is per account: the default keeps a hidden tombstone, and a deployment can choose erasure instead. IMAP synchronization records both dispositions.