Mailbox Audit Log: What It Records and How to Use It

An agent connected through Claude Code, Cursor, Codex, MCP or a REST API has started answering email. Then someone disputes a reply: the recipient wasn't supposed to receive it, the agent may have read a malicious instruction, and the application console shows only a successful tool call.
A mailbox audit log should answer what happened without requiring a developer to reconstruct the incident from terminal output. That means recording reads, policy decisions, human interventions and delivery outcomes as distinct events. It also means being precise about the limits: an authenticated action identifies the credential used, not necessarily the person controlling it, and an accepted send isn't proof that someone received or read the message.
Table of Contents
- Mailbox Audit Log Events at a Glance
- Read Events and Body Access Records
- Sends, Blocks and Suppressions Explained
- Approvals, Handoffs and Mailbox Changes
- How Long to Keep Audit Records
- Querying a Mailbox Audit Log Step by Step
- Audit Gaps and How to Close Them
- Confirm Your Log Covers Every Send
Mailbox Audit Log Events at a Glance
A mailbox audit log is an append-only record of mailbox actions that identifies who acted, what operation ran, when it ran in UTC, which client or API performed it, and what happened.
| Event class | Trigger | Fields to store |
|---|---|---|
| Inbound receipt | Mail arrives from an outside sender | Mailbox ID, message ID, sender, UTC timestamp, screening result |
| Message read or retrieval | Agent or person opens a message | Actor, API key or client, message ID, UTC timestamp |
| Send attempt and outcome | Agent calls the send action | Idempotency key, recipient, result (sent, failed or blocked), correlation ID |
| Duplicate or cooldown block | Policy check before a send | Recipient, rule that fired, ID of the earlier message |
| Suppression match | Recipient is on a suppression list | Recipient or recipient hash, source of the suppression, decision |
| Approval decision | Person approves or rejects a held message | Approver identity, held message ID, decision, UTC timestamp |
| Thread handoff | Conversation passed to a person | Thread ID, handoff actor, return action when the agent resumes |
| Mailbox pause or resume | Bounce or complaint rate crosses a limit, or a person acts | Metric that triggered it, actor, UTC timestamp |
| Policy change | A rule is added, tightened or loosened | Before and after value, actor, reason |
| API key or permission change | Key created, revoked or re-scoped | Key scope, actor, UTC timestamp |
Each row answers an incident question. Receipt establishes whether the mailbox received the suspicious message. Retrieval establishes whether an identity accessed it. Policy and approval events establish which controls ran before an outbound action.
Microsoft Purview mailbox auditing provides a reference model for distinguishing selected actions by mailbox owners, delegates and administrators. Agent systems need similarly explicit attribution, rather than assigning every operation to a shared backend account.
A shared envelope for every event
Every record needs actor and authorization context, mailbox ID, thread ID, operation type, UTC timestamp, source client or API, policy decision, and correlation ID. Include a message ID whenever the operation targets a message, and use an explicit empty value when a field doesn't apply.
Add schema_version, an event identifier and the policy revision used. An investigator must be able to interpret an older decision after the application changes its field names or rule definitions.
Keep message bodies in their message store and audit events in a separate event store. Deleting a message shouldn't erase evidence that it was accessed, and editing a draft shouldn't rewrite an earlier approval record.
- Protect attribution: Record mailbox-scoped identities and effective permissions, never secret key values.
- Protect history: Append corrections as new events instead of modifying existing records.
- Protect evidence: Consider hash chaining or external attestation for high-value records, with deletion controlled separately.
Teams mapping these decisions to an existing Microsoft environment can explore Microsoft 365 audit topics from F1Group for related administrative context.
Read Events and Body Access Records
An illustrative body-access record should be understandable without opening application code:
event: message.body_read
mailbox_id: mbx_example_01
actor: key_agent_support_02
message_id: msg_example_9931
interface: REST
outcome: body_passed_to_model
utc_timestamp: 2025-03-14T09:12:44Z
schema_version: 1
These are example field names and values, not a provider response schema. The essential rule is log body reads separately from metadata reads.
A search returning senders and subjects should emit a metadata-access event. Fetching the message body should emit a body-access event, while a denied retrieval should record that the body remained quarantined.

Distinguish retrieval from model exposure
A mailbox API can establish that it returned content to a client. Unless the model gateway also emits a corresponding event, it can't establish that the client passed that content to a model.
Use body_returned_to_client at the mailbox boundary and reserve body_passed_to_model for instrumentation at the model-input boundary. Connect those events through a correlation ID and an immutable reference to the content version.
Microsoft Exchange mailbox audit documentation describes auditing actions by owners, delegates and administrators, with context that can include the client, host and process. For an agent, that principle translates into identifying the mailbox-scoped credential, interface and actual access outcome.
Record whether the text originated in the new inbound message, an attachment or quoted thread history. Otherwise, a responder may investigate the latest sender even though the malicious instruction arrived in an earlier message included in the model's context.
- Metadata access: Subjects, senders and previews can themselves contain instructions, so record which fields were returned.
- Body access: Store a message-version reference or content hash rather than copying sensitive text into every audit event.
- Quarantine access: Distinguish a safe preview from release of the original content.
What a read event cannot prove
A read event doesn't prove that the model understood an instruction, followed it or caused a later send because of it. That conclusion requires the model-input trace, subsequent tool actions and the policy timeline.
Evidence boundary: Content returned by a mailbox and content submitted to a model are different observations. Logging should name the observation actually made.
Keep body-access evidence in the same retention tier as send and approval evidence. If reads expire earlier, the eventual outbound action may remain visible while its possible source disappears.
Sends, Blocks and Suppressions Explained
A send investigation starts before transport submission. The send path should produce a connected sequence of checks, with each evaluated rule recording its decision and the policy revision.
Walk the send path in order
Look up the idempotency key first. An agent retrying after a timeout shouldn't create another intended message. Record whether the request created a new operation or returned the existing operation's result.
Check duplicates next. Idempotency handles the same request submitted again; duplicate detection handles separate requests that represent repeated outreach. A duplicate event should identify the earlier message and the comparison scope.
Apply the cooldown. Record the recipient, applicable waiting rule and the earlier activity that caused the block. A cooldown without its triggering reference is difficult to distinguish from a configuration error.
Check suppression. Store the suppression source, reason and decision, using a recipient hash where the investigation doesn't require the address itself. The guide to suppression lists and bounce handling for AI senders explains the recipient-state decisions behind this check.
Evaluate sending limits and mailbox state. Keep over-limit and paused results distinct: one describes exhausted capacity, while the other describes an administrative or safety stop. A blocked attempt is still an audit event, even when no transport request exists.
Submit only after the applicable checks pass. Attach the transport identifier to the original correlation ID so later delivery feedback can be joined to the attempt.
Separate acceptance from delivery
Amazon SES deliverability documentation describes quotas covering messages sent in a 24-hour period and messages sent per second, and warns that excessive bounces or complaints can reduce quotas or terminate an account. It also describes global suppression for recent hard bounces: an API-accepted attempt can become a hard bounce rather than a delivery, with suppression lasting up to 14 days.
That distinction should determine event names. transport.accepted means the transport accepted responsibility for processing, not that the recipient's server accepted the message.
Illustrative clean send
send.attemptedconnects the intended message to its idempotency key.policy.evaluatedrecords allow decisions.transport.acceptedsupplies a transport message identifier.delivery.confirmedrecords the delivery signal and its source.
Illustrative accepted send that later bounced
send.attemptedandpolicy.evaluatedestablish the authorized attempt.transport.acceptedrecords the successful submission response.delivery.bouncedrecords the later failure and classification.suppression.updatedrecords any resulting recipient restriction.
Delivery confirmation still doesn't establish inbox placement or human reading. Missing feedback should remain unknown, not be converted into success.
Recipient controls also don't replace content controls. Freeform Company's guide to implementing DLP for email in 2026 provides context for deciding which information should be allowed to leave; an audit record should preserve the content-policy decision without duplicating the sensitive payload.
When someone disputes a send, inspect the correlation ID first. It connects the request, rule decisions, transport acceptance and subsequent outcome.
Approvals, Handoffs and Mailbox Changes
A human decision needs its own event. Recording only the resulting send leaves unanswered whether a person approved that exact content, edited it, or approved a different draft.
For each approval, preserve the approver identity, held message ID, decision, elapsed time from hold to decision and the approved content version. If the person edits the message, retain a restricted version reference or diff, rather than placing the entire body in broadly accessible logs.

Bind decisions to the action authorized
Approval should apply to the recipient set and content version reviewed. If either changes afterward, the previous approval shouldn't authorize the modified message.
A handoff needs similar precision. Record the thread ID, the actor handing it over, the person taking responsibility and the event that returns control to the agent.
- During human ownership: Record any agent reply attempt and the rule that prevented it.
- At return: Capture who returned the thread and which state the agent resumed from.
- For rejection: Preserve the decision even though no outbound message follows.
The implementation choices in human-in-the-loop AI agents matter because a dashboard decision and a backend authorization must refer to the same held action. An approval button isn't evidence unless its result reaches the enforcement path.
Make pause conditions testable
The M3AAWG sender complaint-handling guidance identifies a complaint rate above 0.1 percent, one complaint per 1,000 messages, as a serious sending problem, and says hard-bounce rates above 5 percent can indicate poor list collection or maintenance.
An implementation can adopt those as explicit pause conditions, not assume they're universal provider limits. Count complaints and hard bounces per mailbox, define the measurement window and denominator, and record the observed rate and configured threshold when a rule fires.
Preserve recipient or recipient-hash references and distinguish a preventive block from a bounce observed after submission. A pause event should also identify outstanding work so queued sends don't escape the stop condition.
Molted Email is a mailbox for AI agents, for agents what Gmail is for humans, not an API wrapper or a layer on Resend, Postmark or SES; no separate provider account is required. New mailboxes run autonomously, with optional dashboard approval for first-contact emails or every email; incoming mail is screened before agent access, high-risk originals require human release, and a mailbox paused for rising bounce or complaint rates requires a person to resume it.
Those interventions should remain distinct audit events rather than a generic mailbox update. Approval-setting changes also need attribution, because changing the setting can alter which future messages receive review.
An agent may tighten a rule, but only a person may loosen it. Every policy change should preserve the before value, after value and actor.
How Long to Keep Audit Records
Retention balances operational troubleshooting, security investigation, legal obligations and storage cost. Message retention and audit retention shouldn't be treated as interchangeable: deleting an inbox item and deleting evidence of access are different decisions.
The reference periods below have different scopes. The historical Exchange storage model concerns audit records, while the NIST examples concern email messages.
| Source or standard | Typical retention | Notes |
|---|---|---|
| Exchange mailbox auditing, earlier default | 90 days | Historical Exchange documentation describes the Audits subfolder of Recoverable Items |
| Exchange Audit Standard, current default | 180 days | Microsoft's mailbox audit search guidance describes the standard window; a longer policy can extend it |
| Non-federal organizations, email messages | 30 to 90 days | Common message-retention practice described in NIST email security guidance |
| Federal agencies, email messages | One year or more | Often retained for at least a year in the NIST guidance |
| Agent mailbox audit events | Set by policy and legal duty | Tier by event type, not by age alone |
NIST's electronic-mail security guidance describes 30 to 90 days as common message-retention practice outside federal agencies and a year or more for federal agencies, while advising organizations to consider legal and regulatory requirements. Those examples aren't a retention mandate for an agent mailbox.
Publish tiers with executable rules
A usable policy names event classes, expiry behavior and responsible roles. A statement such as “logs are retained as needed” doesn't tell the storage job what to delete or the incident responder what remains available.
- Operational telemetry: Keep ingestion diagnostics and routine transport processing for a troubleshooting window justified by actual operational needs.
- Security evidence: Retain access, approval, policy, pause and credential events for the investigation window.
- Legally mandated records: Follow the applicable formal schedule, with documented preservation exceptions.
For every tier, define the export format, deletion rights and legal-hold behavior. Name who may place and release a hold, and specify whether it suspends automatic expiry for the relevant records and supporting content references.
Preserve meaning through expiry and export
An export should include the original event, normalized representation and schema version. Otherwise, a future parser may reinterpret a historical rule decision without preserving what the source originally recorded.
Audit coverage begins when logging is enabled; a retention setting can't recover earlier activity. Record the coverage start and any known interruptions alongside the retention policy.
Separate routine expiry from manual deletion, and audit both administrative actions. The role writing events shouldn't also have authority to delete them.
Querying a Mailbox Audit Log Step by Step
The investigation question is narrow: did this agent send this email, and what did the policy decide? Start with identifiers and a bounded time window, then expand only when the evidence requires it.

Reconstruct the decision chain
- Set the mailbox ID and UTC window. Use explicit start and end boundaries, with the end treated as exclusive to avoid overlapping adjacent searches.
- Filter by
send.attemptedto isolate outbound intentions. Include blocked attempts rather than searching only successful submissions. - Add the actor or API key identifier. Resolve that identity's scope at event time, not merely its current permissions.
- Open the matching policy decisions and inspect the rules and policy revision. Distinguish an explicit allow from a missing decision.
- Pull the thread ID and related human events. Match approvals to the exact held message version and inspect handoff ownership at the attempted send time.
- Export the results using cursor-based pagination. Preserve each cursor and completion status so the incident notes establish whether the export reached the end.
- Compare attempts with policy evaluations. Counts provide a preliminary check, but join by attempt or correlation ID to detect unmatched events even when totals happen to agree.
The following command uses an example schema in a JSON Lines export. Its mailbox, actor and timestamps are illustrative investigation values, not an API contract:
jq -c 'select(.mailbox_id == "mbx_example_01" and .utc_timestamp >= "2025-03-14T09:00:00Z" and .utc_timestamp < "2025-03-14T10:00:00Z" and .event == "send.attempted" and .actor == "key_agent_support_02") | {utc_timestamp, message_id, thread_id, correlation_id, policy_decision, outcome}' audit.jsonl
This filter assumes timestamps have been normalized into the same UTC string format. It finds candidate attempts; it doesn't retrieve the corresponding policy, approval or delivery events automatically.
Preserve the export as evidence
Keep the unfiltered export as the incident artifact and treat filtered output as a working view. Record its origin, query boundaries, export completion status and acquisition time.
Calculate a file digest with sha256sum audit.jsonl and place the resulting digest in the incident notes. A matching digest later establishes that the file hasn't changed since hashing, not that the original logging system recorded every event truthfully.
Investigation check: Equal counts don't prove complete enforcement. Every attempt needs its own matching policy decision, and every outcome needs a traceable attempt.
Account for ingestion delay before declaring an event missing, and record the delay observed during the investigation. Late arrivals should update the incident timeline without replacing the original export.
The common boundary mistake is filtering in local time while the records use UTC. Convert the incident time explicitly before querying, or events near the boundary may disappear from the results.
Audit Gaps and How to Close Them
| Gap | Why it happens | Fix |
|---|---|---|
| Send recorded with no policy evaluation event | Send path skips the check | Route every send through one policy call and alert on any send without a matching decision |
| Shared service account for all agents | Actions cannot be attributed to one agent | Issue a mailbox-scoped identity per agent |
| No records before logging was enabled | Audit coverage is not retroactive | Enable logging before go-live and record the start date |
| Quarantine release by a script | Held mail leaves quarantine without a person | Restrict release to a person and log the approver |
| Local time in timestamps | Events cannot be merged across systems | Store UTC and a schema version with each record |
| Audit deletion by the writing role | Records can be altered by the process they describe | Restrict deletion to a separate administrative role |
| Ingestion lag and dropped events | Incidents are investigated with missing events | Track lag and dropped-event counters with alerts |
A logged send doesn't establish a complete audit trail. A process can submit mail successfully and crash before writing its decision, or a retry worker can bypass instrumentation present in the normal request handler.
Detect missing relationships
Alert on a recorded send without a matching policy evaluation after the permitted ingestion delay. Match using stable identifiers, not merely nearby timestamps or recipient addresses.
Also alert when quarantine state changes without a recorded human approver. A generic administrator identity or script execution record doesn't establish that a person authorized release of that original message.
Track the health of the evidence pipeline itself:
- Ingestion lag: Compare event occurrence time with storage arrival time.
- Dropped events: Monitor producer and collector rejection counters.
- Clock skew: Compare timestamps from participating systems against their expected time source.
- Audit access: Record searches, exports and administrative access to the evidence store.
A send path should define what happens when audit persistence fails. A transactional outbox can preserve a durable intent alongside application state, but external transport still requires reconciliation after uncertain responses.
Classify failures before changing recipient state
Suped's bounce-management guidance recommends suppressing a confirmed permanent recipient failure after one occurrence, counting at most one recipient failure per calendar day per reason family, and counting each message-recipient pair once rather than counting intermediate retries.
Keep authentication, reputation, throttling and blocklist failures in a sender-wide category. They aren't evidence that the destination address is invalid.
Write both the original transport response and the normalized classification into the event, with the classifier version. Otherwise, a later classification change can make a historical suppression impossible to explain.
Retry and cooldown events should reference the intended message rather than inflate recipient-failure counts. A provider outage mustn't become a reason to suppress valid contacts.
A logging gap is an incident finding, not a footnote. Record its scope, affected time window and remediation owner alongside the mailbox investigation.
Confirm Your Log Covers Every Send
A go-live review should exercise enforcement and evidence together. A successful test is not just a delivered message, but an export that explains the permitted and prevented actions.

Run the release checklist
- Check every send path. Include replies, scheduled work, retries and administrative tools in the policy-enforcement review.
- Confirm each agent has its own mailbox-scoped identity, with permissions that match its job.
- Validate event serialization. Confirm UTC timestamps, stable identifiers and a readable schema version in the exported records.
- Set the retention tier and name the legal-hold owner.
- Exercise blocked and approved paths. Trigger a duplicate block and a human-approved send, then find both decisions and their associated attempts in the export.
- Enable alerts for missing policy events and ingestion lag, with a named response owner.
- Run the investigation query. Reconstruct the test window without relying on application memory or terminal history.
The send, check, deliver and track workflow provides a useful framing for validating the whole outbound operation rather than only the send response.
Completion should leave an engineer able to identify who attempted what, which rule applied, what the transport reported and who authorized any held action. Any unanswered field belongs on the release issue list, with a clear owner.
Molted Email provides hosted agent mailboxes with an audit log recording sends, policy blocks, approval decisions and mailbox changes. Start free at molted.email, free for people, with paid plans from $19 a month, and use a blocked-send test to verify the evidence before enabling a production workflow.