Task 5 one-line registry descriptions on the 8 biggest skills (remediation-tool,
gc-audit, packetdial, memory-dream, human-flow, self-check, impeccable,
mailprotector); skill-description injection ~3320 -> ~2123 tokens (~36%),
keyword triggers preserved, frontmatter valid.
Task 7 thinned /save + /sync bodies to point at sync.sh (single source) instead of
re-documenting internals; Phase 0 save-vs-sync, cross-user notes, exit-75
reporting kept verbatim; mechanical sync never depends on an LLM step.
Task 10 session-logs/YYYY-MM/ forward convention for new logs (scoped-grep recall,
no monolithic index); existing flat logs untouched (grep covers both).
Bash now-phoenix.sh helper (fixed UTC-7 epoch math; replaces unreliable
TZ=America/Phoenix date that silently returns UTC on Git-Bash).
P0 (1.2.0) + Task 6 CLAUDE split + Task 9 delegation (1.3.0) already shipped.
Spec: specs/claudetools-harness-optimization/plan.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.4 KiB
name, description
| name | description |
|---|---|
| mailprotector | Manage the ACG Mailprotector CloudFilter email-security gateway (emailservice.io). Search/release held/quarantined mail (in+outbound), pull mail-flow logs (why a message did/did not deliver), inspect + manage allow/block rules. Read-only default; releases/rule-changes gated --confirm. Triggers: mailprotector, cloudfilter, held/quarantined mail, release email, allow/block rule, INKY. Live production. |
Mailprotector / CloudFilter Skill
Standalone CLI client for the Mailprotector CloudFilter REST API
(emailservice.io), the reseller email-security platform ACG layers on top of
client mail flow. Read-only by default; every write (release, rule add, config
change) is gated behind --confirm.
The two-layer context (important)
ACG's email security sits in front of client mailboxes as two cooperating layers:
| Layer | What it does |
|---|---|
| Mailprotector CloudFilter | The delivery / filtering gateway. Inbound and outbound mail passes through it; spam, virus, and policy hits are held / quarantined here. Releasing a held message re-injects it for delivery. This is the API this skill drives. |
| INKY | Email annotation / phishing-banner layer. Adds the warning banners and protects against impersonation. Not part of this API surface. |
Both sit layered on top of the client's own Exchange / M365 mail flow — so a
"missing email" investigation usually means: was it held at CloudFilter (check
messages / logs), or did it pass CloudFilter and stall in Exchange?
Connection
| Item | Value |
|---|---|
| Base URL | https://emailservice.io/api/v1 (override MAILPROTECTOR_API_BASE_URL) |
| Auth | Authorization: Bearer <api_key> |
| Vault entry | msp-tools/mailprotector.sops.yaml, field credentials.api_key |
| Env override | MAILPROTECTOR_API_KEY |
Credential resolution order: MAILPROTECTOR_API_KEY env -> vault
credentials.api_key. The key is never hardcoded; a clear setup error is raised
if neither resolves.
Scopes
Five entity types carry logs / messages / configuration /
allow_block_rules / users / domains sub-resources. Path form is
/{scope}/{id}/...:
resellers, customers, domains, user_groups, users
The CLI validates scope against this set.
Running the CLI
This machine's Python launcher is py (per identity.json); python / python3
also work. Run from the scripts dir so the two modules resolve.
cd C:/claudetools/.claude/skills/mailprotector/scripts
py mp.py status # validate token (GET /domains, per_page=1)
py mp.py domains # list domains (global)
py mp.py domains --scope customers --id <id>
py mp.py domain <domain_id>
py mp.py customers <reseller_id>
py mp.py customer <customer_id>
py mp.py users <scope> <id>
py mp.py user <user_id>
py mp.py find-user user@client.com # locate a user / alias by email (a READ)
py mp.py config <scope> <id> # shows permissions.messages.allow_spam_release
py mp.py rules <scope> <id>
Mail-flow logs and held mail (the common investigation)
Both accept the same filters: --sender --recipient --subject --decision --sort-field --sort-direction --page --page-size.
# Why didn't this arrive? Look at the decision in the flow logs.
py mp.py logs domains <domain_id> --recipient ceo@client.com --decision quarantine_spam
# Held / quarantined mail search.
py mp.py messages domains <domain_id> --sender boss@vendor.com
--decision values: default, deliver, quarantine_spam,
quarantine_virus, quarantine_policy, bounce, encrypt, delete.
--sort-field values: @timestamp (default), prime.direction,
prime.from_header_raw, prime.recipient, prime.subject, prime.decision,
prime.score.
Writes (gated)
Every mutating command prints a [DRY RUN] line and exits non-zero unless you
pass --confirm.
py mp.py release <message_id> --confirm
py mp.py release <message_id> --recipients alt@client.com --confirm
py mp.py release-many <scope> <id> --ids 111,222,333 --confirm
py mp.py release-many <scope> <id> --all --confirm
py mp.py add-rule <scope> <id> --value vendor.com --type allow --confirm
py mp.py enable-release <scope> <id> --confirm
The allow_spam_release gotcha
Releasing a held spam message fails if the owning entity does not have
permissions.messages.allow_spam_release = true. Workflow:
py mp.py config <scope> <id>— checkallow_spam_release.- If
false:py mp.py enable-release <scope> <id> --confirm. - Re-run the
release/release-many.
Virus and policy quarantines are governed separately — only spam release is gated by this permission.
Example workflow: find a client's held outbound mail from a sender and release it
# 1. Find the client's domain.
py mp.py domains --scope customers --id <customer_id>
# 2. Search held messages from the sender (outbound = sender is the client user).
py mp.py messages domains <domain_id> --sender user@client.com --decision quarantine_spam
# 3. If it's spam-held, make sure release is permitted on the domain.
py mp.py config domains <domain_id> # check allow_spam_release
py mp.py enable-release domains <domain_id> --confirm # only if needed
# 4. Release by message id (DRY RUN first — omit --confirm to preview).
py mp.py release <message_id> # [DRY RUN]
py mp.py release <message_id> --confirm # actually release
Raw escape hatch
The named commands cover the common surface; for anything else, hit the path
directly. Non-GET methods still require --confirm.
py mp.py raw GET domains/<id>/logs
py mp.py raw POST messages/<id>/deliver --body '{"include_original_recipients":1}' --confirm
Notes
- This is the LIVE production reseller CloudFilter platform. A release
re-delivers real mail to real recipients, and an allow rule can let real spam
or phishing through — confirm the target entity with a read command before any
write, and prefer releasing specific message ids over
--all. - Pagination:
page(default 1) andper_page(default 25); resellermessagescapsper_pageat 50. TheX-Paginationresponse header carries the page/total metadata. - Full endpoint catalog, filter tables, and the global
field[op]=valueoperators live inreferences/api.md.