Skip to content

helpdesk

Helpdesk inbox: an anonymous browser client (GET /helpdesk/client.js) submits questions, contact requests, bug reports and wishes behind a honeypot, a minimum submit time and a single-use proof-of-work challenge; signed-in users file reports from the admin. Every request is stored platform-wide and every SystemAdmin is notified by mail, optionally also in a Telegram chat (option telegram, off by default); the notification names only the kind, the app and a link, never name, address, phone, subject or message, because mail and Telegram cannot be shredded. SystemAdmins reply by mail (Reply-To set, attachments, quoting the original) and mark requests done; a website sender gets a fixed acknowledgement, a signed-in reporter a copy and an in-app note. Personal data is crypto-shredding-ready and the EXT_USER_DATA hooks export and erase a user’s requests.

From recipes-helpdesk — the smallest working mount:

// 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

helpdesk feature preview

What this feature needs to run (Requires, top) and the write commands this feature provides (Provides, bottom).

flowchart TB
  n_helpdesk["helpdesk"]
  subgraph how_reqs["Requires / optional"]
    n_config["config"]
    n_delivery["delivery"]
    n_user_data_rights["user-data-rights"]
    n_files["files"]
    n_notes_history["notes-history"]
    n_channel_telegram["channel-telegram"]
  end
  subgraph how_provides["Provides"]
    n_cmd_helpdesk_write_forward_request(["forward-request"])
    n_cmd_helpdesk_write_mark_done(["mark-done"])
    n_cmd_helpdesk_write_notify_reporter_in_app(["notify-reporter-in-app"])
    n_cmd_helpdesk_write_reply(["reply"])
    n_cmd_helpdesk_write_report(["report"])
    n_cmd_helpdesk_write_send_reply_mail(["send-reply-mail"])
    n_cmd_more(["+1 more"])
  end
  n_config --> n_helpdesk
  n_delivery --> n_helpdesk
  n_user_data_rights --> n_helpdesk
  n_files --> n_helpdesk
  n_notes_history --> n_helpdesk
  n_channel_telegram -.->|optional| n_helpdesk
  n_helpdesk --> n_cmd_helpdesk_write_forward_request
  n_helpdesk --> n_cmd_helpdesk_write_mark_done
  n_helpdesk --> n_cmd_helpdesk_write_notify_reporter_in_app
  n_helpdesk --> n_cmd_helpdesk_write_reply
  n_helpdesk --> n_cmd_helpdesk_write_report
  n_helpdesk --> n_cmd_helpdesk_write_send_reply_mail
  n_helpdesk --> n_cmd_more

Provides — write commands this feature registers (dispatch them through the command bus):

Start with recipes-helpdesk for a step-by-step walkthrough with runnable code and integration tests.

Per-tenant config keys, set via the tenant-admin UI or a seed. 🔒 = encrypted at rest.

KeyTypeDefaultScopeWho can writeWho can read
reply-totext""systemSystemAdminSystemAdmin
  • Registers extension: userData → helpdesk-request, userData → helpdesk-reply