pithy support

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

FlagDefaultPurpose
--worker <name>the project’s only WorkerThe 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
--jsonfalseMachine-readable output

deprovision

FlagDefaultPurpose
--worker <name>—The app Worker whose wrangler.jsonc names the database the audit trail is written to
--storagefalseIrreversible. 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_CREDENTIALSRequired with --storage
--r2-secret-access-key <key>R2_CREDENTIALSThe secret half of the pair
--jsonfalseMachine-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:

  1. Preflight. Verify the account can host a Workflow at all.
  2. Bucket. Create or reuse <project>-global-support. One bucket for the project rather than one per environment: the SUPPORT_BUCKET binding 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 reports skipped.
  3. Workers. Deploy the prebuilt classification worker for every managed environment. It reads a message and writes a label over the AI binding.
  4. 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.
  5. 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

KeyTypeMeaning
command"support provision"The subcommand that produced this line
bucketobjectThe R2 bucket, and what happened to it
bucket.bucketstringThe bucket name. Reported even when the step was skipped, so the name is always readable
bucket.createdbooleanTrue only when this run created it
bucket.skippedbooleanTrue when attachments are off and raw retention is off, so no bucket was wanted
environmentsstring[]The environments a classification worker was deployed for
searcharrayWhat the full-text index did, per environment
search[].envstringThe environment this entry describes
search[].createdbooleanThe index was created and backfilled in this run
search[].droppedbooleanThe index was dropped in this run. Both false means it already matched the config
routingobjectWhat happened to the inbound Email Routing rule
routing.createdbooleanTrue only when this run created the rule
routing.skippedbooleanTrue when no routing flags were supplied

support deprovision

KeyTypeMeaning
command"support deprovision"The subcommand that produced this line
storageDeletedbooleanWhether --storage was passed
routingZonestring or nullThe 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 --json

Take 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.
ESC