Email Security setup with the CLI¶
Private beta
Email Security is in private beta. It is not generally available, and
access is enabled per organization — if the ext-email-security extension
is not in your catalog, this product is not turned on for you yet.
While it is in beta, expect the surface described here to move: commands, fields and event shapes may change between releases, and they may change in ways that are not backwards compatible. Pin a CLI version if you script against it, and re-read this page after upgrading.
Talk to us before relying on it in production.
Prefer the web app? Start with the console walkthrough.
Before running commands, install and configure the CLI and select your organization. $OID below means your LimaCharlie organization ID.
This reference takes an organization from zero to a populated Email Security queue: enable the product, connect a mail tenant, verify the connection, and read the first judged message. You can do all of it in the console or entirely as code — both are shown.
1. Enable Email Security¶
Email Security is enabled per organization by subscribing to the
ext-email-security extension. The subscription is the enable gate: without it
every /v1/mailsec/* route is refused, and the console shows a subscribe screen
instead of the product.
Confirm it:
Subscribing also seeds the recommended policy records — all in alert_only
mode, so nothing moves mail until you say so. See Policy Reference.
Free trial: 14 days, 25 mailboxes
An organization on the LimaCharlie free tier gets Email Security in full for 14 days and protects up to 25 mailboxes while it does. Every feature is the same as on a paid plan; only the duration and the mailbox count differ. The clock starts the day you subscribe and does not restart if you unsubscribe and resubscribe, so point the 25 at the mailboxes that matter — start with the executives, finance and the abuse mailbox.
When the trial ends, ingestion pauses and nothing is deleted; the data is removed 30 days later unless the organization moves off the free tier, and you are told before that happens. The full rules, and the exact fields to read the countdown from, are in Plans, the free trial, and the mailbox cap.
2. Grant the permissions¶
Email Security ships four permissions. A user or API key that will triage mail
typically needs mailsec.get, mailsec.set and mailsec.act; a read-only
analyst needs only mailsec.get. mailsec.get.eml is an escalation on top of
mailsec.get and should be granted deliberately — see
Overview → Permissions.
Managing the connection itself additionally needs the Hive permissions for
mailsec_provider and secret.
3. Prepare the provider credential¶
The credential is created in your mail provider's admin console and stored in the LimaCharlie secret Hive. It is always referenced, never inlined into the connection record.
| Provider | What you create | Full walkthrough |
|---|---|---|
| Microsoft 365 | An Entra ID app registration with application permissions and a client secret | Microsoft 365 |
| Google Workspace | A Google Cloud service account with domain-wide delegation, plus a Pub/Sub topic and subscription in the same project | Google Workspace |
The console renders your own setup guide
The setup steps, OAuth scopes and gcloud commands are served by the
product rather than transcribed here, so they cannot go stale: the
connection wizard renders them with your project id and service-account
address already substituted, and each step names the connection-test check
that proves it was done. These pages carry the narrative — what each grant
buys and what breaks without it — and the wizard carries the values.
The same guide is available headless:
4. Connect the mail tenant¶
In the console¶
Open Email Security → Settings and add a connection. The wizard collects the provider, the saved credential name, the mailbox scope and — for Google Workspace — the project ID used to name its Pub/Sub topic and subscription. It shows the personalized setup guide alongside, and saves the connection. The connection diagnostic then opens if you have permission to test; click Run connection test to verify access. Failed saves are reported inline on the review step, with the validator's own wording rather than a generic error.
Editing an existing connection is patch-preserving: fields the form does not manage are left exactly as they were.
As code¶
One mailsec_provider Hive record per connection. The record's existence (and
its Hive enabled flag) is the connection — there is no separate on switch.
cat > m365-credential.json <<'JSON'
{"tenant_id": "<tenant-id>", "client_id": "<application-client-id>", "client_secret": "<the-secret-value>"}
JSON
limacharlie secret set --key m365-mail \
--value "$(cat m365-credential.json)" --enabled --oid $OID
secret set wraps the value into the secret record's {"secret": "..."}
envelope for you.
# m365.yaml
provider: m365
credentials: hive://secret/m365-mail
ingest:
mode: auto # Microsoft 365 auto is Graph notification push
backfill_days: 14
features:
outbound_observation: true
reports_mailbox: phishing@corp.example
limacharlie hive set --hive-name mailsec_provider --key m365-prod \
--input-file m365.yaml --enabled --oid $OID
New Hive records are created disabled
hive set creates a record disabled unless you pass --enabled. A
disabled mailsec_provider record is not a connection — nothing is
discovered, subscribed or ingested — so a first connection that appears to
do nothing is usually this.
The full field reference — scope, provider-specific delivery mode, features — is in Connecting Providers.
5. Verify the connection¶
The connection test uses the credential the only way a credential can be verified: by using it. Each requirement is reported independently, so a failure names the step to fix rather than saying "connection failed".
ok: true
summary: Connection is fully configured.
checks:
- id: credential
name: Authenticate to Microsoft Graph
required: true
status: passed
- id: mailbox_read
name: List mailboxes in the tenant (24 found)
required: true
status: passed
- id: mail_write
name: "Modify mail (Mail.ReadWrite): quarantine, restore, banner"
required: true
status: passed
- id: mail_send
name: "Send mail (Mail.Send): reporter auto-replies"
required: false
status: skipped
detail: Mail.Send is not granted; reporter auto-replies will be refused by name until it is
A failed optional check is not an error and ok stays true: a tenant that
deliberately declined the optional grant has a working connection, and the
product tells you by name which capability it does not have rather than
pretending it does. Every failed check carries a remediation string naming the
exact fix.
For Google Workspace, add --include-watch to verify notification delivery end
to end. It is the one probe with a side effect: it establishes a real Gmail
watch, which is idempotent and expires on its own.
6. Watch coverage fill in¶
Coverage is the product's honesty surface. It reports mailboxes in four separate
states — protected, discovered, excluded, error — and never collapses
them, because a broken subscription hiding behind a deliberate exclusion is
exactly how a coverage number starts lying. connections.state summarizes to
the worst connection, and an organization with no connection at all reads
unconfigured, never ok.
The same call reports message volume and the verdict funnel over a window, the parse-degradation rate, backfill progress, the emission backlog, open reports, active campaigns, and the effective automation mode.
It also carries an entitlement block: which plan the organization is on, when
a trial ends, how many mailboxes it may protect against how many it is
protecting, and — if one is scheduled — the date its Email Security data will be
deleted and what cancels it. On a trial organization this is where you check
that the 25 mailboxes are the 25 you meant:
entitlement:
plan: trial
trial_ends_at: "2026-09-20T14:02:11Z"
trial_days_remaining: 12
mailbox_cap: 25
mailboxes_active: 25
mailboxes_over_cap: 118 # discovered, not protected
mailbox_cap_reached: true
mailboxes_over_cap is the number that matters: those mailboxes were found and
are not being watched. Narrow the connection's scope, or move off the free
tier. See
Plans, the free trial, and the mailbox cap.
Backfill is judged, and acts on nothing
On connection, the collector walks up to ingest.backfill_days (14 by
default) of existing mail. It judges that history with the same rules it
judges live mail with, so the queue has real verdicts on your first day and
a hunt or a rule backtest has something to run against — and it seeds sender
profiles and campaign statistics, so "we have never heard from this sender"
is a true statement on day two instead of day ninety.
It takes no actions and emits no telemetry on that history. No
EMAIL_MESSAGE, no EMAIL_VERDICT, no policy automation and no
remediation: mail delivered eleven days ago has already been read and filed
by the person it was addressed to, and quarantining it now — or replaying a
fortnight of it into your D&R rules on the day you switch the product on —
is not something you asked for. The drawer says so on each such message
(judged_via: backfill).
It is also paced, so it cannot compete with live ingestion: a small
tenant's fortnight fills in over hours, a very large estate's over days,
rather than all at once. Progress is reported in coverage.
7. Read the first judged message¶
limacharlie mailsec message list --oid $OID --limit 10 --output table
limacharlie mailsec message get <msg_uuid> --oid $OID --output yaml
In the console, Email Security → Messages is the queue and the row opens a drawer with the verdict, the signals that produced it, authentication results, links, attachments, the sender profile, the action timeline and the remediation controls. See Messages & Triage.
8. Decide whether the product may act¶
Setup grants access to read and modify mail, and establishes notification subscriptions. Default automation does not change messages. Automations ship in alert_only, which
means a rule is evaluated, its intent is recorded, and the mailbox is not
touched. Analyst-initiated actions from the console, CLI or API always execute
— alert_only withholds automation, not people.
Turning enforcement on is a deliberate edit to a mailsec_policy/automations
record. Read Policy Reference before you do, in
particular this consequence:
Enforcement is currently an organization-level switch
The remediation executor authorizes automated action when any automation
rule in the organization is in enforce mode. Which rule dispatches an
action is still decided per rule, but the executor's consent check is not
per rule — so putting one rule into enforce enables automated action for
the organization's automated paths generally. Enable it when you mean the
organization to start moving mail.
Next steps¶
- Tune what is judged: Detections & Verdicts
- Write your own rules: Custom Rules
- Turn the abuse mailbox into an SLA queue: User Reports
- Correlate mail with the rest of your telemetry: Events & Automation