--- name: mailprotector description: "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 ` | | 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. ```bash cd C:/claudetools/.claude/skills/mailprotector/scripts bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py status # validate token (GET /domains, per_page=1) bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domains # list domains (global) bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domains --scope customers --id bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domain bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py customers bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py customer bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py users bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py user bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py find-user user@client.com # locate a user / alias by email (a READ) bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py config # shows permissions.messages.allow_spam_release bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py rules ``` ### 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`. ```bash # Why didn't this arrive? Look at the decision in the flow logs. bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py logs domains --recipient ceo@client.com --decision quarantine_spam # Held / quarantined mail search. bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py messages domains --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`. ```bash bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release --recipients alt@client.com --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release-many --ids 111,222,333 --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release-many --all --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py add-rule --value vendor.com --type allow --confirm bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py enable-release --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: 1. `bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py config ` — check `allow_spam_release`. 2. If `false`: `bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py enable-release --confirm`. 3. 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 ```bash # 1. Find the client's domain. bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domains --scope customers --id # 2. Search held messages from the sender (outbound = sender is the client user). bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py messages domains --sender user@client.com --decision quarantine_spam # 3. If it's spam-held, make sure release is permitted on the domain. bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py config domains # check allow_spam_release bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py enable-release domains --confirm # only if needed # 4. Release by message id (DRY RUN first — omit --confirm to preview). bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release # [DRY RUN] bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release --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`. ```bash bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py raw GET domains//logs bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py raw POST messages//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) and `per_page` (default 25); reseller `messages` caps `per_page` at 50. The `X-Pagination` response header carries the page/total metadata. - Full endpoint catalog, filter tables, and the global `field[op]=value` operators live in `references/api.md`.