Files
claudetools/.claude/skills/mailprotector/SKILL.md
Mike Swanson b66843096a sync: auto-sync from GURU-5070 at 2026-06-19 08:40:35
Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-19 08:40:35
2026-06-19 08:41:57 -07:00

7.5 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/didn't 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.

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

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 <id>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domain <domain_id>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py customers <reseller_id>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py customer <customer_id>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py users <scope> <id>
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py user <user_id>
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 <scope> <id>         # shows permissions.messages.allow_spam_release
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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.
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py logs domains <domain_id> --recipient ceo@client.com --decision quarantine_spam

# Held / quarantined mail search.
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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.

bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release <message_id> --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release <message_id> --recipients alt@client.com --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release-many <scope> <id> --ids 111,222,333 --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py release-many <scope> <id> --all --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py add-rule <scope> <id> --value vendor.com --type allow --confirm
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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:

  1. bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py config <scope> <id> — check allow_spam_release.
  2. If false: bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py enable-release <scope> <id> --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

# 1. Find the client's domain.
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py domains --scope customers --id <customer_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 <domain_id> --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 <domain_id>          # check allow_spam_release
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py enable-release domains <domain_id> --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 <message_id>                # [DRY RUN]
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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.

bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" mp.py raw GET  domains/<id>/logs
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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) 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.