Config
categories is classification; the guard and the verdicts are provenance; inboundAddresses is bounded by the MX constraint. Refusals carry an ordinary error code.
support({
inboundAddresses: ["support@help.example.com"],
ai: { enabled: true, model: "@cf/meta/llama-3.1-8b-instruct" },
categories: defineSupportCategories({ /* yours, merged over the eight */ }),
guard: {
maxRawBytes: 2_000_000,
maxPerSenderPerHour: 20,
maxPerHour: 500,
archiveSpam: true,
},
})Until inboundAddresses is set the inbox is inert — it ignores every message and says so once in the log.
Enums
Priority — urgent (today) · normal (this week) · low (when there is time)
Sentiment — angry · frustrated · neutral · positive
Direction — inbound (arrived from the customer) · outbound (a reply this Worker sent)
Channel — email · app
Account link source — session · email_address · null
Categories — billing · account_access · bug_report · feature_request · abuse_report · privacy_request · spam · uncategorized, plus your own.
Routes
| Route | Verified by |
|---|---|
POST /support/feedback | user — open or append to your own thread |
GET /support/feedback | user — my threads |
GET /support/feedback/:id | user |
GET /support/threads | control-plane |
GET /support/threads/:id | control-plane |
GET /support/replies | control-plane |
POST /support/threads/:id/archive | control-plane |
POST /support/threads/:id/reply | control-plane |
POST /support/threads/:id/reclassify | control-plane |
POST /support/threads/:id/flags | control-plane |
Scopes
| Scope | |
|---|---|
support:threads:read | |
support:threads:archive | |
support:threads:reply | |
support:threads:reclassify | |
support:threads:flag |
Error codes
| Code | |
|---|---|
support/not_found | |
support/invalid_category | A declared key outside the effective taxonomy |
support/rejected | The guard refused it |
support/unparseable_message | |
support/classification_failed | |
support/reply_failed |
Thread columns worth knowing
| Column | |
|---|---|
declaredCategory | The submitter’s claim. Written once, never by the classifier |
category | The model’s judgment. Overwritten on every classification |
accountLinkSource | session, email_address, or null |
channel | On the thread and on every message |
emailJobId | Present exactly when a row is outbound and email. Ask channel, not this |
Threading
On In-Reply-To and References. Never on the subject.
An app submission is minted a message id on the way in, so a mailed reply threads back rather than opening a new conversation.
Recipient matching
On the SMTP envelope recipient. Never on a To: header.
What provisioning creates
An R2 bucket (only if something writes to it), a classification Worker per environment, and — only when you pass all three routing flags — an Email Routing rule named <project>-global-support-inbound.
License
MIT, like most of the kit. Nothing here is unlocked by a hosted dashboard — every route is reachable with a credential you issued.