Skip to content

Limits

What Kiku deliberately does not do. Every entry here is a decision, not a gap waiting to be filled — read it before you build a workflow on top of an assumption.

One instance. Never two.

Do not scale the app to two replicas. Realtime updates are an in-process pub/sub feeding Server-Sent Events, so with two containers each agent’s browser is connected to exactly one of them and sees only the events that happened to land there. Half your inbox stops updating, and nothing errors.

There is no clustering flag to get wrong; the only way to reach this state is to add deploy.replicas or run the image twice against one database, so don’t. A single container handles the volume a shared support inbox produces with room to spare.

The upgrade path, if it is ever needed, is Postgres LISTEN/NOTIFY — no new service.

No antivirus scanning

Attachments are stored and served without being scanned, and there is no ClamAV service in the Compose file.

The server never executes attachments. The exposures that actually exist are handled without a scanner: files are served with Content-Disposition: attachment, X-Content-Type-Options: nosniff and a Content-Type we choose rather than one the sender claims, and email HTML is sanitised with remote resources proxied. A required ClamAV service is 1–2 GB of RAM — heavier than the app itself — for a boundary that the agent’s own endpoint protection already is.

The attachment volume is a plain directory. If you have a compliance requirement, run clamscan against it on a cron.

Agents cannot reply by email

Agent notification mail is read-only, with a deep link into the app: replying to a Kiku notification does not post a message.

This is not laziness. Matching From: against a known agent would let anyone who learns an agent’s address post as them, into a product where internal notes sit inline with customer messages. And every message in Kiku is explicitly public or internal, never inferred — an emailed reply carries no such signal, so it would need a magic body command, where forgetting the marker mails an internal note to the customer. That is the worst bug this product could ship.

Mobile is answered by the UI being responsive.

MCP clients sign in with a token, not OAuth

An MCP client authenticates with an access token sent as a bearer header. Kiku has no OAuth authorization server, so a hosted connector that only knows OAuth sign-in cannot connect. Through MCP a client never sees a ticket summary, never marks a ticket read, and never changes who a reply goes to. See MCP.

Browser notifications need an open tab

Kiku shows a browser notification only while the desk is open in a tab, foreground or not. It has no service worker and sends nothing through a browser vendor’s push service, so a closed tab hears nothing, and the email notification is the one that reaches you then.

Microsoft 365 and Google Workspace mailboxes

Kiku polls IMAP with a username and password, and ships no OAuth app for either. A mailbox that requires OAuth cannot be connected directly — forward it into the subdomain address instead. See Coming from Microsoft 365.

The auto-acknowledgement has no knobs but one

You can turn the automatic reply to new tickets off in Settings → Email. Everything else about it is fixed, and none of it is exposed:

  • One acknowledgement per ticket, on creation only. Follow-ups are never acknowledged.
  • Never sent to a machine. Mail declaring Auto-Submitted, Precedence: bulk/list/junk, a List-Id or List-Unsubscribe header, a null return path, a Message-ID in our own shape, a delivery report, or an upstream spam verdict (X-Spam-Status: Yes or Microsoft SCL 5-9) gets a ticket but no reply.
  • Rate limits, hardcoded: 4 acknowledgements an hour and 10 a day per sender address, 30 an hour per sender domain, 200 an hour across the instance. Over any of them, the reply is withheld and the ticket is unaffected.
  • 60 inbound messages an hour from one address. Past that, further mail from that address is quarantined rather than ticketed — never dropped; it is still there to read.

These numbers are what stops your instance amplifying a mail loop or a mail bomb at someone else. A setting that could raise them would have exactly one use.

The acknowledgement text is likewise fixed, and English only.

The Contact form has one limit

A client IP can send 30 Contact form submissions an hour and 100 a day, counted across every form and organization on the instance. Past either number the form tells the visitor to email the support address instead, and Kiku stores nothing. IPv6 clients count per /64. The limit is hardcoded, and there is no per-form or per-organization limit.

The limit keys on the client address, so behind a reverse proxy it needs TRUSTED_PROXIES. Without it, every visitor shares the proxy’s address and one limit.

It stops one network from flooding a form. It does not protect your quota: a submission opens a billable ticket like an email does, and someone rotating addresses can spend quota through the form with nothing else to stop them.

AI: where your ticket content goes

AI classification is off unless you configure it in Settings → AI. There is no default provider and no key ships with the app.

It runs after every customer message and every public reply, about 20 seconds later, and answers the ticket’s type, area and outcome — and its company, if it has none — from your own lists. It fills a field only when it is empty or was last set by the classifier, so it can never overwrite an agent, and a model that times out, errors or answers with something that is not one of your names leaves the field as it was. Every night at 03:00 UTC it also runs on tickets with an empty field and something from the last 7 days it never got an answer for: a message whose run failed or that arrived while classification was off, or a ticket closed without an outcome. A run that keeps failing is tried each night until the event is a week old. That nightly run sends the same ticket content. There is no “classify” button.

When the sender plainly works for a company you have not added, read from the footer at the end of their mail or from their domain, the classifier creates that company and files the ticket under it. It first reuses a company of the same name, or one that claims the sender’s domain, so a second person from the same company lands under the first one. Whenever it files a ticket under a company that claims no domain yet, new or existing, that company claims the sender’s domain. It never claims a freemail provider’s domain such as gmail.com or web.de, nor one your own agents sign in with.

With a hosted provider configured, the subject, the sender’s address and up to 24 messages of the thread are sent to that provider on each run: the first message and the newest, internal notes included, each cut to 2,000 characters. That is customer email and your team’s notes leaving your server, and the statement is exactly that plain because a half-working scrubber would sell a privacy guarantee the code does not keep.

If that is unacceptable: point the base URL at a local model — Ollama, vLLM, anything speaking the OpenAI-compatible API — or leave the key unset and the feature stays off. Nothing else in Kiku depends on it.

Also true, and by design

  • No downgrade path. See Upgrade and backup.
  • No env fallback for anything in Settings. Mailbox configuration cannot be declared in a manifest. See Configuration reference.
  • Seven ticket statuses, fixed — New, Backlog, In Progress, Ready, Blocked, Waiting On Customer, Closed. Not configurable.
  • English only. No i18n in v1, including the auto-acknowledgement text.
  • /setup is an open claim. Between first boot and claiming the admin account, anyone who can reach the app can take it. Claim it before you expose the instance.