Recipe: helpdesk
Public contact form backend. The helpdesk bundled feature stores questions, contact
requests and problem reports from website visitors and notifies every SystemAdmin (mail,
Telegram only when the app turns it on). The notification names only the kind, the app and a link into the inbox:
no name, address, phone, subject or message. Telegram is a third party where nothing can be
shredded or recalled, so it gets the same minimal text. Website senders get a fixed
acknowledgement that repeats nothing they typed; signed-in admin reports get a copy.
SystemAdmins reply from the admin. Phone is off and Telegram is not mounted unless the app
turns them on (fields.phone, telegram: true).
What the recipe demonstrates:
- Mounting
createHelpdeskFeaturewith its companions (config, delivery, user-data-rights, files, notes-history) - The anonymous flow:
helpdesk:query:challengehands out a proof-of-work challenge,helpdesk:write:submitverifies it; spam gets the same 200 body and stores nothing - The browser client at
GET /helpdesk/client.js(window.KumikoHelpdesk.load()/submit())
Layout
Section titled “Layout”samples/recipes/helpdesk/├── package.json├── README.md└── src/ ├── feature.ts # createHelpdeskFeature options └── __tests__/ └── feature.integration.test.ts # challenge query + client routeThe behavior (submit, silent drops, replies, Telegram, GDPR export and erasure, retention) is
covered by the integration tests next to the feature in
packages/bundled-features/src/helpdesk/__tests__/.
Run tests
Section titled “Run tests”bun test --config=bunfig.integration.toml samples/recipes/helpdeskIntegration into a real app
Section titled “Integration into a real app”- Mount
createHelpdeskFeature({ appName })plus delivery, a mail channel,user-data-rights,fileswith a file provider,notes-historyandconfig. - Run
kumiko schema generate(new tables, plus the deliveryidempotency_keycolumn). - SystemAdmins set
helpdesk:config:reply-toin the admin. - Embed
/helpdesk/client.json the website; the form is yours. The client posts to the relative paths/api/queryand/api/writewithout cookies, so the page has to share the app’s origin. A website served by another server forwards/apito the app on its own origin; it can then serve the same script fromHELPDESK_CLIENT_SOURCEand type its calls withHelpdeskWebsiteClient, both from@cosmicdrift/kumiko-bundled-features/helpdesk/client.samples/apps/use-all-bundled/public/helpdesk-contact.html(withhelpdesk-contact.js) is a complete same-origin page.
Telegram is optional and off by default: notifications go by mail to SystemAdmins only. An app
that turns it on (telegram: true) also mounts channel-telegram and secrets for the bot
token, and SystemAdmins set helpdesk:config:telegram-chat.
Source code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Helpdesk sample//// A public website form (question, contact request, problem report) that lands with the// SystemAdmins: mail, optional Telegram, a copy to the sender. Spam is handled without a// captcha: honeypot field, single-use proof-of-work challenge, minimum submit time, per-IP// and per-email rate limits. Rejected submits answer 200 with the normal body.//// The website embeds the anonymous client the feature serves itself. The client posts to the// relative paths /api/query and /api/write, so the page must share its origin with the app// (or forward /api to it on its own origin):// <script src="/helpdesk/client.js"></script>// const { kinds, phone } = await window.KumikoHelpdesk.load();// const result = await window.KumikoHelpdesk.submit({ kind, name, email, message, locale });// // result.kind: "sent" | "rate-limited" (retryAfterSeconds) | "failed"//// Prerequisites the app wires itself:// - `anonymousAccess` (defaultTenantId = SYSTEM_TENANT_ID) so the form needs no login// - delivery with a mail channel (channel-telegram only with `telegram: true`, off by default)// - config, user-data-rights and notes-history (required features)// - files with a file provider, because the entities carry file fields// - `kumiko schema generate` for the new tables
import { createHelpdeskFeature } from "@cosmicdrift/kumiko-bundled-features/helpdesk";
export const helpdeskFeature = createHelpdeskFeature({ appName: "Sample App", fields: { phone: "optional" }, // Lowered only to keep the sample test fast; the default (19 bits, 3 seconds) suits production. spamProtection: { proofOfWorkBits: 8, minSubmitSeconds: 0 },});📄 On GitHub: samples/recipes/helpdesk/src/feature.ts