User Reports¶
Generally available
Email Security is generally available. Subscribe to the Email Security extension in your organization to purchase and enable it. Paid usage costs \(1 per protected mailbox per month**, billed daily at **\)1/30 per mailbox-day. Free-tier organizations receive a limited 14-day trial. See security product billing for pricing, trial limits and upgrading.
CLI examples require LimaCharlie CLI 5.7.0 or later, which includes the
mailsec commands. Install or upgrade, then check:
Credential-file examples also require jq.
Your own people are the best detector you have for the mail that got through. Email Security turns an abuse mailbox into an SLA queue: reports are joined back to the original message across the whole tenant, robots that mail the abuse address are kept out of the numbers, and the reporter can be told what happened.
Setting it up¶
Designate an abuse mailbox on the connection record:
Anything delivered to that mailbox is read as a report. Point your existing report path at it — a "report phishing" button that forwards, a mail rule, or just an address people know.
The abuse mailbox must not be a mailbox people report from
A report is a message forwarded to the abuse address. One mailbox cannot be both the sender and the destination of the same forward, so keep the abuse address separate from the mailboxes your users send from.
The reported message arrives as a forward carrying the original as a nested message. The parser handles the nesting, and the original is located across every protected mailbox by its internet message id — so a campaign that hit forty people is found from one person's report.
What happens to a report¶
- An
EMAIL_USER_REPORTevent is emitted and a report row is created. - The original message is located across the tenant and joined to the report.
- The original's campaign, if it has one, is attached.
- The original is stamped
user_reported. That flag outranks a benign verdict in the triage queue — a human flagging something is the strongest free signal the product gets. - If reporter replies are enabled, the reporter is sent a templated acknowledgement.
Reports have three states: open, triaging, resolved.
The queue¶
limacharlie mailsec report list --status open --oldest-first --oid $OID
limacharlie mailsec report get <report_id> --oid $OID
--oldest-first is what makes this an SLA surface rather than a feed. "The
oldest thing nobody has looked at" is the question a queue exists to answer, and
it is not answerable from a newest-first page.
In the console, Reports carries the queue, per-row age and resolution, SLA tiles computed from exact sources, a drawer timeline (reported → joined → actioned → reporter replied) and pivots to the message and its campaign.
original_found¶
Every report says whether its original was located. A report whose original was never indexed is a real state, not a blank field: the mail predates the connection, or it landed in a mailbox outside the connection's scope. It is shown as a gap so you can tell "we could not find it" from "we did not look".
Resolving¶
| Disposition | Meaning |
|---|---|
malicious |
Confirmed threat |
spam |
Unwanted spam |
graymail |
Bulk or promotional mail |
benign |
Reviewed as safe |
simulation |
Authorized security simulation |
Resolving sets the linked message's independent disposition with source: report
and emits EMAIL_REPORT_RESOLVED. It preserves the engine verdict. A report whose
original is unavailable can still be resolved; its coverage gap remains visible.
Pure resolution requires mailsec.set.
To remediate as part of resolution, also hold mailsec.act. Choose --scope
message, --scope group, or --scope campaign and an --action. The first request returns
remediation_preview and leaves the report open. Read the affected messages and
mailboxes, then repeat the same request with --confirm <token>. Failed, withheld,
or interrupted remediation leaves the report open and returns the action outcome.
Campaign remediation is bounded to the existing 500-message sweep limit. A missing
original or campaign is refused rather than guessed.
Remediate the same message across recipients¶
--scope group targets copies of the reported original delivered to different
recipients. A campaign targets similar messages; a group represents the same
message. Group actions use a durable, paged job that scales beyond 500 messages.
Only the reported original receives the resolution disposition.
Choose a UUID attempt once, then reuse it through preview, confirmation and polling. Preparation freezes the recipient selection before returning a token; messages arriving afterwards are excluded. For example:
ATTEMPT=$(python3 -c 'import uuid; print(uuid.uuid4())')
limacharlie mailsec report resolve "$REPORT" --disposition malicious --scope group --action quarantine_message --attempt "$ATTEMPT" --wait --oid "$OID" --output json
# Read remediation_preview.job and take remediation_preview.confirmation as TOKEN.
limacharlie mailsec report resolve "$REPORT" --disposition malicious --scope group --action quarantine_message --attempt "$ATTEMPT" --confirm "$TOKEN" --wait --oid "$OID" --output json
Keep the action, disposition, reason, force and attempt unchanged when confirming
or resuming. Changing them needs a fresh preview. --wait polls for up to 300
seconds; a timeout exits with code 2, retains the durable job in the response and
leaves the job running. Repeat the same confirmed command to resume. The report
stays open until every selected message succeeds or is already in the requested
state. Failed or withheld members leave it open; retry them through a new attempt
and confirmation. A force override also needs a fresh preview with --force.
If you leave after confirming, remediation continues; finish resolution by
resuming the same request.
Resolving an already-resolved report succeeds and reports already_resolved, so
two analysts clicking at once is not an error.
benign also repairs the sender's history
Resolving a report as benign subtracts that message's contribution from the
sender's flagged-history counter. Without it, one wrong flag would keep
weighing on every later message from a legitimate correspondent. The repair
is guarded per message across dispositions, resolutions, and releases, so retries
do not remove another message’s contribution. A malicious disposition credits
that message once; neither operation changes the engine verdict.
Reopening¶
A resolved report goes back to open and is worked again. The command takes no
options beyond the report id: who reopened it is stamped from your
authenticated identity and cannot be supplied by the caller.
The resolution columns are deliberately kept. Only the status moves, so the
row still reads "previously resolved benign by system:automated-sender"
rather than erasing the very thing being disputed — and reopened_from names the
state it came out of.
To remediate a group after reopening, start a new attempt and preview. The previous resolution's confirmation cannot adopt its completed recipient selection.
Reopening a report that is already open or triaging succeeds and reports
already_open, so two analysts clicking at once is not an error. An unknown
report id is an error rather than a silent success, because this names one
specific row to change.
Reopening needs mailsec.set — exactly the permission the resolve it undoes
needs, and deliberately not a wider one. An analyst who can close a report must
be able to reopen one, or a mis-click is permanent.
This is the escape hatch the auto-resolver depends on
A report from an automated sender is born resolved and
attributed to system:automated-sender. That classifier is only defensible
because it is reversible: without a reopen, one wrong call about an
organization's mail would close real reports permanently. It serves a wrong
AI resolution and an analyst's mis-click equally — none of the three is
special here.
Automated senders¶
An abuse mailbox receives a great deal that is not a report: vendor service notices, ticketing and calendar robots, delivery-status notifications for mail the mailbox itself sent, and list traffic the address was subscribed to years ago. Left alone, each one becomes an open queue item with no reported message behind it, ageing in the SLA numbers next to real reports — and, with reporter replies on, gets mailed back to an address that cannot receive mail.
So a message whose sender says it is a machine produces a report row that is
born resolved, attributed to system:automated-sender.
Five triggers, each independently sufficient, and every one of them is a statement the sending system made about itself:
| Reason | What it is |
|---|---|
no_reply_local_part |
The address itself says replies go nowhere — no-reply, no_reply, No.Reply, donotreply, do-not-reply, and vendor variants like <product>-noreply@…. Matched on the local part only, tokenized on its separators, so juno.reply does not join them |
auto_submitted |
RFC 3834: any Auto-Submitted keyword other than no means the message was generated automatically |
precedence_bulk |
The pre-RFC convention: Precedence: bulk, junk or auto_reply |
list_id |
RFC 2919: the message came from a mailing list — a distribution mechanism nobody clicks "report phishing" from. Carve-out: if the list-id names your own abuse mailbox (many organizations run the abuse address as a group), it does not count |
null_return_path |
RFC 5321's null reverse path, required on every delivery-status notification. The sender is asserting this message must never be bounced |
This is not a filter:
- The message is still ingested, still indexed, still judged, and its raw copy is still stored.
- An
EMAIL_USER_REPORTevent is still emitted and a report row still exists. - The reasons are recorded on the report, so "auto-resolved because the sender set
Precedence: bulk" names the thing to argue with, rather than an unactionable "auto-resolved".
Two things it deliberately does not do:
- It does not stamp
user_reportedon the original. No human flagged anything. A delivery-status notification really does carry the message it bounced, and manufacturing the product's strongest human signal out of a robot — in the one place a reader cannot check — would be worse than the noise it removes. The join is still recorded on the report; only the claim about who made it is withheld. - It never overrules a human. The classifier gets exactly one attempt at a
report. A report an analyst has touched — reopened, put in
triaging, or resolved themselves — is left alone, which is what makes a reopen stick. That is the whole basis on which auto-resolving is defensible.
The system: prefix cannot be a real principal, so an analyst reading the
resolved queue can tell at a glance that nobody looked.
Reporter replies¶
Off by default: it sends mail on your behalf, to your own staff.
policy_type: reporter_reply
enabled: true
acknowledgement: "Your report was received and is being reviewed."
on_resolve: true
templates:
malicious: "Your report has been reviewed and classified as malicious."
spam: "Your report has been reviewed and classified as spam."
graymail: "Your report has been reviewed and classified as graymail."
benign: "Your report has been reviewed and classified as benign."
simulation: "Your report was an authorized security simulation."
enabledcontrols the receipt acknowledgement. Its default wording is neutral; it never claims a verdict, a resolution, or a completed provider action.on_resolvecontrols a separate reply after resolution. Templates are keyed by disposition, not engine verdict; missing templates fall back to the recorded disposition. Customize wording to state only outcomes your workflow verifies.- Acknowledgement and templates are plain text, at most 4096 UTF-8 bytes, with no
<or>. Resolution replies use fixed, escaped rendering. - Sending needs
Mail.Sendon Microsoft 365 orhttps://mail.google.com/on Google Workspace. Automated senders never receive either reply. - Resolution deliveries use a durable claim. A retry of one resolution does not
send another reply; reopening and resolving again records a new resolution.
If a provider-send outcome is uncertain, delivery is marked
ambiguousand is not automatically resent. The report detail exposesresolution_reply_status. - Replies carry a loop marker. The guard also checks that the sender is the abuse mailbox, because a sender-controlled header alone cannot establish a real reply.
Automating on reports¶
EMAIL_USER_REPORT is ordinary telemetry, so a D&R rule can act the moment
someone reports something — page a channel, open a case, or drive remediation
through the extension. See Events & Automation.