Email Security setup with the CLI¶
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.
Prefer the web app? Start with the console walkthrough.
Use a CLI build that includes the commands described above, then configure authentication and select your organization. $OID below means your LimaCharlie organization UUID, not its display name. Use limacharlie org list --output yaml to find it and set OID="<organization-uuid>" for these examples.
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. Subscribing is the purchase. Paid usage is \(1 per
protected mailbox per month**, billed daily at **\)1/30 per mailbox-day on that
day's protected-mailbox count. See security product billing.
Confirm it:
Subscribing installs the default detection rules in dr-mail. It does not
create automation policy records. With no automation policy, automatic actions
are off; a new automation rule defaults to alert_only, so it records intent
without moving mail. 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 after a 30-day purge grace period unless the organization moves off the free tier before the purge, 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.
The free tier means a configured sensor quota of 2 or less. Raising it
above 2 moves the organization to a paid plan, lifts the trial limits,
and starts usage billing. Check mailsec coverage and its entitlement
block for the trial countdown and any scheduled deletion.
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.
For setup, ask your administrator for the permissions that match your tasks:
| Task | Permissions |
|---|---|
| Subscribe | billing.ctrl, user.ctrl |
Read, create or edit connections (including --enabled on a data write) |
mailsec_provider.get, mailsec_provider.set |
| Enable or disable an existing connection without editing its data | Read access to its metadata (mailsec_provider.get.mtd or mailsec_provider.get), and mailsec_provider.set.mtd or mailsec_provider.set |
| Create and enable a credential secret in one write | secret.set |
| Select existing secrets and read their metadata | secret.get.mtd; reading secret values separately needs secret.get |
| Test a connection | mailsec.act |
| Read messages and coverage | mailsec.get |
| Change mail rules or policy | mailsec.set |
Deleting a connection additionally needs mailsec_provider.del. Read-only
analyst access does not grant permission to change which tenant or mailboxes
the product reads.
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:
limacharlie mailsec onboarding --provider gworkspace --oid $OID
limacharlie mailsec onboarding --provider m365 --oid $OID
Fill the Google commands in advance with your project details:
limacharlie mailsec onboarding --provider gworkspace \
--project-id "$GCP_PROJECT" --sa-email "$SERVICE_ACCOUNT_EMAIL" \
--topic mailsec-gmail-push --subscription mailsec-gmail-push-sub \
--oid "$OID" --output yaml
Set the two variables to the project and service-account email from your downloaded key. The CLI uses these values to populate the onboarding instructions.
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
jq -Rs '{secret: .}' m365-credential.json \
| limacharlie secret set --key m365-mail --enabled --oid $OID \
&& rm -f m365-credential.json
jq -Rs builds the secret record's {"secret": "..."} envelope without
putting the credential in process arguments. The temporary file is removed only
after a successful write.
# m365.yaml
provider: m365
credentials: hive://secret/m365-mail
scope:
include_addresses:
- pilot@corp.example
ingest:
mode: auto # Microsoft 365 auto is Graph notification push
backfill_days: 14
features:
outbound_observation: true
Replace pilot@corp.example with your pilot mailbox addresses. If you later
configure features.reports_mailbox, use an existing mailbox and include it
in this scope too; otherwise user reports cannot arrive. Omitting scope
or leaving its include lists empty covers every discovered mailbox, subject
to exclusions and any domain filter. include_addresses and exclude_addresses
entries must contain @; domains entries must be bare domains containing a dot,
with no @ or slash. Exclusions win over inclusions.
ingest.backfill_days accepts 0–90, default 14. 0 disables the initial
connection-setup history pass; it does not delete already indexed mail and is
not a privacy cutoff for recovery. If a Gmail history ID or Graph delta token
expires, recovery can re-walk the default 14-day window even when this value
is 0. See backfill cleanup
before changing retention to remove indexed history.
Read the enforcement model before enabling actions:
connections start with alert-only automation, and manual actions in an alert-only
organization need an explicit --force override.
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. Manual actions are also withheld in an alert-only organization:
force_required: true asks for explicit consent. Repeat with --force in the
CLI or JSON force: true in the API to perform that action without enabling
organization-wide automation. See manual overrides.
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