Stand up the support inbox: the R2 bucket attachments and raw messages live in, the per-environment classification worker, the full-text index, and — only when you name all three parts — the Email Routing rule that delivers the support address to your app worker.
This is the provisioning half of the support capability — provisioning Support has the account steps, and running a support inbox is the day-to-day.
Synopsis
pithy support provision [--worker <name>] [--routing-zone <zone-id>] [--inbound-address <address>] [--app-worker <name>] [--json]
pithy support deprovision [--worker <name>] [--storage] [--routing-zone <zone-id>] [--r2-access-key-id <id>] [--r2-secret-access-key <key>] [--json]Both subcommands reach a Cloudflare account. There is no local mode and no --env flag: provisioning spans every managed environment in one run. Unlike pithy email and pithy media, no Secrets Store id is needed — the classification worker binds DB and AI and holds no credential to decrypt.
Flags
provision
| Flag | Default | Purpose |
|---|---|---|
--worker <name> | the project’s only Worker | The app Worker whose wrangler.jsonc carries the per-environment DB binding |
--routing-zone <zone-id> | — | Cloudflare Zone ID of the subdomain receiving the mail. Email Routing must already be enabled on it |
--inbound-address <address> | — | The exact recipient address the rule matches. It must also be listed in support()'s inboundAddresses, which is what claims it |
--app-worker <name> | — | Deployed name of the production app worker with the support capability composed |
--json | false | Machine-readable output |
deprovision
| Flag | Default | Purpose |
|---|---|---|
--worker <name> | — | The app Worker whose wrangler.jsonc names the database the audit trail is written to |
--storage | false | Irreversible. Also delete the R2 bucket with every attachment and raw message in it — this is your support history |
--routing-zone <zone-id> | — | The zone the inbound rule lives on. Without it the rule is left in place and mail keeps arriving |
--r2-access-key-id <id> | R2_CREDENTIALS | Required with --storage |
--r2-secret-access-key <key> | R2_CREDENTIALS | The secret half of the pair |
--json | false | Machine-readable output |
The three routing flags are all or nothing. Pass all three and the rule is created; pass none and everything else provisions without one. Pass one or two and the run is refused before anything is created — an operator who passed two of three asked for a rule, and provisioning everything but the step that delivers the mail would look like success and receive nothing.
The refusal exists because enabling Email Routing on a zone points its MX at Cloudflare. A rule created on the wrong zone moves an adopter’s real inbound mail off their existing provider, which is not a mistake a provisioning command gets to make on somebody’s behalf. Use a subdomain zone, never your apex.
What it does
provision, in order — and the order is the inverse of how it fails:
- Preflight. Verify the account can host a Workflow at all.
- Bucket. Create or reuse
<project>-global-support. One bucket for the project rather than one per environment: theSUPPORT_BUCKETbinding hangs off the app worker that receives the mail. When the capability is configured with attachments off and no raw retention, no bucket is created and the step reportsskipped. - Workers. Deploy the prebuilt classification worker for every managed environment. It reads a message and writes a label over the
AIbinding. - Search index. Create or drop the full-text index in each environment’s app database to match
search.fts. A provisioning step rather than a migration, because the index is derived — every row in it comes from the messages table. A newly created index is backfilled immediately, so the inbox never answers no matches for a term plainly in a body. - Routing rule. Last, and only with all three routing flags. Creating it first would start delivering mail to a Worker whose classification host is not deployed yet — a window in which real customer messages arrive and stay uncategorized with nothing to say why.
Each environment’s deploy needs the app DB id from that environment’s stanza. A missing stanza or id is refused rather than deploying a worker that would write its classifications into nothing.
deprovision removes the routing rule (only with --routing-zone) and the classification workers. The bucket stays unless --storage is passed, and with it the key pair is resolved before the first worker comes down.
--json
support provision
| Key | Type | Meaning |
|---|---|---|
command | "support provision" | The subcommand that produced this line |
bucket | object | The R2 bucket, and what happened to it |
bucket.bucket | string | The bucket name. Reported even when the step was skipped, so the name is always readable |
bucket.created | boolean | True only when this run created it |
bucket.skipped | boolean | True when attachments are off and raw retention is off, so no bucket was wanted |
environments | string[] | The environments a classification worker was deployed for |
search | array | What the full-text index did, per environment |
search[].env | string | The environment this entry describes |
search[].created | boolean | The index was created and backfilled in this run |
search[].dropped | boolean | The index was dropped in this run. Both false means it already matched the config |
routing | object | What happened to the inbound Email Routing rule |
routing.created | boolean | True only when this run created the rule |
routing.skipped | boolean | True when no routing flags were supplied |
support deprovision
| Key | Type | Meaning |
|---|---|---|
command | "support deprovision" | The subcommand that produced this line |
storageDeleted | boolean | Whether --storage was passed |
routingZone | string or null | The zone id the rule was removed from, or null when --routing-zone was not passed and the rule was left in place |
Errors
Incomplete routing flags.
The inbound routing options are incomplete.
Pass --routing-zone, --inbound-address, and --app-worker together, or none of them.The capability is not configured. No Worker’s pithy.config.ts composes support.
The capability will not load. Distinct from the above, and classified rather than assumed — not installed, could not be loaded, or installed and will not load.
Credentials are missing.
The project has no name. The bucket is found by name and reused, so a guessed name would adopt another project’s inbox.
The app Worker’s environment is not wired.
Half an R2 key pair with --storage, or none at all. Both refused before a worker comes down.
Several Workers and no --worker. The resolution error pithy add raises.
Examples
Provision everything but the rule, then add it once the zone is decided:
$ pithy support provision
Bucket acme-global-support ready.
2 classification workers deployed.
Search index created in staging, prod.
No routing rule. Pass --routing-zone, --inbound-address, and --app-worker to create one.
Done.pithy support provision --routing-zone 0a1b2c3d --inbound-address support@help.example.com --app-worker acme-prod --jsonTake the workers down but keep the history and the rule:
$ pithy support deprovision
Support workers removed.
The routing rule was left in place. Pass --routing-zone to remove it.
Done.