For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /reference/docs/decisions/0062-support-tickets-direct-zammad.md.

0062 — Support tickets, iteration 1: direct requests to Zammad

Status: Accepted. Date: 7 October 2026. Acting on behalf of the customer (X-On-Behalf-Of) was replaced by an agent-only token in decision 0063; the rest stands.

A linked Pharos user creates support tickets and reads them as a conversation. Iteration 1 keeps no ticket state in Pharos. Every request goes straight to Zammad while the user waits, on behalf of the user's linked Zammad customer (Zammad's X-On-Behalf-Of), so Zammad only ever shows or creates that customer's tickets. Zammad is authoritative for tickets and their messages.

This is a deliberate exception to the durable hand-off rule in AGENTS.md. If Zammad is slow or Beto stops during a creation, nothing records the attempt. Creation therefore retries only when Zammad clearly never received the request (connection refused, 429, 503) and only within five seconds; after a timeout it reports that the ticket may exist and does not retry. A user who tries again anyway may create a duplicate, which an agent merges. Iteration 2 replaces this with a ticket aggregate, an outbox hand-off and a search before create.

Only users whose SupportCustomer is linked may create, list or open tickets. The user comes from the session: the API authenticates the browser, then forwards X-Pharos-Subject with Beto's key; forwarded headers are rebuilt, so a browser cannot name a user. The ticket's customer is always the user's SupportCustomer, never a browser value and never Zammad's guess:<email>.

Closing follows the same pattern: Beto reads the ticket as the customer, then sends only state: closed. Acting as the customer, Zammad would also accept title, priority and owner changes, so no browser field is ever passed through. Closing a closed ticket changes nothing, so closure retries timeouts, 5xx, 408 and 429 within five seconds, unlike creation.

Replies post a public, plain-text message as the customer, after reading the ticket as the customer. Like creation, a reply is not safe to repeat: it is retried only when Zammad never received it, and a timeout is reported as "may have arrived". Closed tickets accept no replies in iteration 1. Zammad would take a customer's reply on a closed ticket but leave it closed, so agents could miss it; reopening by replying is a separate later flow.

The ticket list and the conversation are read from Zammad when they open. No tickets read model or Zammad webhook exists in iteration 1; both belong to iteration 2 with live updates. Internal notes are removed in Beto as well as by Zammad. Agent messages are converted to plain text and the browser renders text only.

This binds the first public backend route: /api/v1/support/* on the API maps to /v1/support/* on Beto. Beto accepts in-cluster traffic only from the API.

Page source

Source snapshot · Revision f928834c8.