Seed catalogue · Support
Brook
Email-first support with recorded customer evidence and operator-approved Stripe refunds.
- Alternative to
- Freshdesk, under MIT
- Family
- Support
- Edition
- Brook
- Version
- 0.2.0
- Contract
- 92 clauses across 12 areas
- Maturity
- Bounded operator-approved refund workflow verified locally (Workers/D1 and desktop/mobile browser); hosted provider sandbox and full retained contract acceptance pending.






6 screenshots from the product's own visual and verification artifacts. Its handover records what was exercised.
What you get
Everything below is generated from the seed's own manifest and contract. No rounded-off claims, no numbers we made up.
Operating cost
Estimated, not free by default
The seed's own cost declaration, unedited.
- Status
- not measured
- Currency
- USD
- Assumptions
- One business deployment; usage depends on traffic and email/refunds.
- Excluded
- Cloudflare, SendGrid, Stripe fees
Externals declared
3 externals, each with a reason
Under BASE-DATA-004, user data leaves only through what is declared here.
- cloudflare-platform
- Cloudflare Worker, D1 and optional Email Routing host owner-controlled application data and inbound email.
- sendgrid
- Delivers operator-approved customer mail and native sign-in links.
- stripe
- Authoritative payment evidence and explicitly approved refunds.
Extension points
2 declared integration conveniences
These hooks are optional conveniences. Owners may change any source, including core logic, schema and infrastructure; their agents adapt useful upstream fixes and verify the result.
Declared limits
Capacity, stated up front
Straight from seed.json → limits. Where a value is an estimate the seed says so.
- Status
- unverified-load-capacity
- Note
- No load capacity claim; inbound parsing refuses messages over 2 MB.
- Inbound Message Bytes
- 2,000,000
Non-goals, stated proudly
A product that says what it will never do is a product you can trust to stay small, fast and legible. These are refusals, not gaps.
- multiple workspaces or tenants
- granular roles, groups, or private queues
- chat widgets, social, SMS, voice, or a public help centre
- outbound campaigns or a generic workflow builder
- automatic semantic ticket merging
- autonomous AI replies, state changes, or external actions
- external commerce mutations other than refunds
- a reporting module beyond the figures the situation board defines
- a library of operator-maintained canned replies
- marketing engagement signals such as email open and click rates
- native mobile applications
- arbitrary outbound webhooks
- a built-in payment or commerce system
- malware scanning
The contract
All 92 clauses of Brook, from its own CONTRACT.md. Stable IDs identify requirements; the source handover and verification evidence distinguish implemented, tested and uncovered behavior.
HOME1 clause
GET / presents the configured owner name, one configured statement of what the internal support workspace is for, and a sign-in link to /signin. It contains no pricing, feature tour, testimonials, metrics, or other marketing fabrication. When the configured credit toggle is enabled (its default), the footer contains the plain static link Powered by Brook · runeditrun.com; rendering it makes no request to that site, the seed author, or any other third party.
OPS3 clauses
Every operator can see and act on every ticket, customer, view, and setting except where a policy restricts a specific action. There are no roles, and no filtered list or assignment restricts who may open or act on a ticket.
Operators are invited and removed in settings. Any operator may invite. Any operator may remove any other operator, except the operator configured at setup, whom only they themselves can remove. A removal that would leave the deployment with no operator is rejected. Removing an operator unassigns their tickets and leaves their past messages, notes, and audit entries attributed to them. Policy: operator.management.v1; default: any operator may invite or remove within these rules. A replacement may narrow who may invite or remove; it can never permit last-operator removal.
Every ticket mutation includes the expected ticket revision. A stale revision returns 409 with the current revision and creates no message, property change, status change, external action, or other partial effect.
MAIL16 clauses
An inbound message that passes MAIL-002 and parses as an RFC 5322 message becomes a ticket, or appends to an existing ticket under MAIL-004, within 60 seconds. It creates no ticket when MAIL-014 quarantines it or CUST-006 blocks its sender. The stored message preserves the sender, every recipient, the subject, the text and sanitised HTML bodies, the threading headers, the provider's received time, and the accepted attachments.
Every mail-provider webhook, including intake and delivery-status callbacks, is processed only after its configured signature is verified; an invalid signature returns 401 and creates no record, job, ticket, message, or attachment.
Re-delivery of the same provider event or RFC Message-ID produces one inbound message and records the duplicate as a no-op.
An inbound email appends to a ticket only when its In-Reply-To header, its References header, or the ticket reference of TKT-016 identifies that ticket and the sender is a known participant under MAIL-013; otherwise it creates a new ticket. Sender, subject, or body similarity alone never merges mail. The activity timeline records which of the three identifiers matched.
One RFC message addressed to more than one configured support address creates one inbound message. The ticket records every matched address and the single address it will reply from, which is the first match in the order the addresses are configured.
Submitting a reply with a new idempotency key atomically creates one pending outbound message and one dispatch job. Reusing the key returns the original message and creates no additional dispatch.
Provider acceptance changes the message to accepted and records the provider message ID and threading headers within 60 seconds; acceptance does not mean delivery. Definitive transient failures are retried with the same provider idempotency key for at most five total attempts during one hour. A permanent failure becomes failed; an ambiguous outcome becomes needs_review and is not retried automatically.
A verified provider report changes an accepted message to delivered, delayed, bounced, or rejected and records the report time. Brook never labels a message delivered without an authoritative delivery report.
An inbound message is non-actionable when it carries an Auto-Submitted header whose value is not no, a List-* header, Precedence: bulk, or a delivery or read receipt, or when its sender's local part is no-reply, noreply, mailer-daemon, or postmaster. Non-actionable mail is retained and shown on the ticket but does not reopen it, notify operators, or trigger AI. Every other inbound customer message is actionable; clauses elsewhere use these two terms with this meaning.
Accepted inbound attachments are private ticket attachments. An attachment rejected by the limits in seed.json leaves the message readable and records the filename and rejection reason; outbound attachments travel within those same limits.
Brook acknowledges a verified intake callback only after a durable intake record exists. Failure before that returns 503 so the provider redelivers, and creates no partial ticket. A parse, storage, or enqueue failure after the intake record exists marks that record visibly failed with a retryable or terminal reason.
A new inbound ticket records the sender as requester and non-support To and Cc addresses as participants; Bcc recipients are never disclosed. Operators may add or remove participants. A reply addresses the requester and current participants, excluding configured support and no-reply addresses.
For MAIL-004, a known participant is the current requester or participant. A removed or unknown sender does not qualify even when the message contains a valid threading header or ticket reference.
A verified message with no configured support destination is quarantined without creating a ticket or message and displays the received destination and quarantine reason to operators.
A reply or acknowledgement is sent from the address the ticket recorded under MAIL-005, with the display name and signature configured in settings appended as the final block of the body, and carries the ticket reference of TKT-016 in the subject and in a Brook-issued header. Operator notifications instead use the no-reply sender of NOTE-005 and carry no signature.
When acknowledgement is enabled in settings, the actionable message that creates a ticket produces exactly one automatic acknowledgement, stored as an outbound message on the ticket and subject to MAIL-006 through MAIL-008. No acknowledgement is sent for a ticket an operator created (TKT-014), for non-actionable mail (MAIL-009), for a blocked customer (CUST-006), or a second time on the same ticket.
TKT17 clauses
A ticket is new, in_progress, waiting_on_customer, on_hold, or resolved. “Resolve & Close” sets resolved; closed is not a separate state. Every status change records the actor, previous state, next state, and time.
An actionable message from an unblocked customer on a resolved, waiting_on_customer, or on_hold ticket atomically appends the message and changes the ticket to in_progress. Policy: ticket.reopen.v1; default: retain the assignee and recalculate deadlines.
Operators can set priority (low, medium, high, or urgent), issue type, assignee, status, case-insensitive canonical tags, and configured ticket custom fields. Issue types and closure reasons are chosen from lists operators edit in settings. Each change records the actor, previous value, new value, and time in the activity timeline.
New tickets are assigned by the assignment policy, and operators can assign or reassign them. An assignee removed under OPS-002 leaves the ticket unassigned. Policy: ticket.assignment.v1; default: leave new tickets unassigned.
A reply is customer-visible and enters the email delivery lifecycle. A private note is visually distinct from a reply, is never delivered to the requester or a participant on any channel, and is never used as the body of a reply or acknowledgement. Notes are included in the export required by BASE-DATA-001.
Resolving a ticket requires an explicit confirmation that shows the reply, the status change, the closure reason selected from the settings list, and any external action. The result records each part independently with its own outcome, so a failed email or external action is never presented as successful, and the stored closure reason is the one confirmed.
Operators can list tickets by any combination of status, priority, assignee, tag, issue type, source, deadline tier (QUEUE-002), and customer, with counts derived from the same filters.
The default order is breached first, then nearest active deadline, then priority from urgent to low, then most recent customer activity, then ticket reference. Operators may instead sort by newest or oldest customer activity, with ticket reference as the final tiebreak.
Search covers ticket reference, subject, message text, note text, customer and participant names and email addresses, and surfaced order, charge, and refund identifiers, labels the field that matched, and returns within one second.
Operators may irreversibly merge duplicate tickets by choosing a primary; messages and attachments are ordered chronologically, each secondary ticket becomes a link to the primary and keeps its own reference, no customer message is sent, and the actor and reason are audited. Policy: record.destructive-actions.v1.
Deleting a ticket removes its messages and attachments under BASE-DATA-003. Policy: record.destructive-actions.v1.
A ticket is unread for an operator until they open it after the latest customer message; queue and navigation counts use that operator-specific state.
Each ticket presents one chronological timeline of inbound messages, replies, notes, property and participant changes, threading decisions, delivery attempts and delivery reports, AI analyses, external actions, follow changes, feedback results, merges, and references to deleted records.
An operator can create a new ticket for an existing customer with a subject and optional private note. Its source is manual; a ticket created from inbound mail has source email, and these are the only two values TKT-007 filters on. Creation sends no customer message; any later reply follows the MAIL-006 delivery lifecycle.
An operator can reopen a resolved ticket to in_progress after confirmation; the action retains its history and previous closure reason, records the actor and reason, and recalculates deadlines under ticket.reopen.v1.
Every ticket has a reference that is unique for the life of the deployment and never reused after deletion or merge. It is shown wherever the ticket appears, carried in outbound mail under MAIL-015, matched by MAIL-004, and accepted by search under TKT-009.
An operator follows a ticket once they are assigned it, reply to it, or add a note to it, and any operator may follow or unfollow any ticket. Followers are the audience for the ticket notifications named in NOTE-001; unfollowing stops those notifications for that operator only and changes nothing else about the ticket.
CUST9 clauses
The first actionable inbound email from an address matching no customer creates one customer and links the ticket to it.
Email addresses are compared case-insensitively after normalisation, and one address identifies one customer unless an operator explicitly merges records.
A customer has name, email addresses, phone, address, preferred channel, preferred language, marketing preference, segment, tags, and configured customer custom fields; operators can edit them with every change audited.
A customer profile lists their tickets with reference, subject, status, assignee, last update, and feedback rating, and their satisfaction score under FB-004. When adapters are configured it also lists recent orders, payments, refunds, spend, and the provider retrieval time of each. Every figure shown is derived from data the deployment holds or from a named adapter response.
Operators may irreversibly merge two customers by selecting the surviving record; tickets, notes, and provider links follow it, conflicting fields are resolved explicitly, and the action is audited. Policy: record.destructive-actions.v1.
An operator can block a customer. Inbound mail from a blocked customer is retained as non-actionable blocked activity on the ticket MAIL-004 selects, or as a visible blocked-intake record when MAIL-004 selects none. It creates no ticket, reopen, operator notification, acknowledgement, or AI work. Unblocking affects only later messages.
Deleting a customer removes their tickets, messages, notes, attachments, feedback results, and locally stored commerce references under BASE-DATA-003; retained audit entries anonymise the customer reference. Policy: record.destructive-actions.v1.
An operator can create a customer with a name and at least one normalised email address. If that address already identifies a customer, Brook opens the existing record and creates no duplicate.
Operators can add, edit, and delete dated notes on a customer. A customer note records its author and time, is never sent to the customer, is attached to no ticket, and is included in the export.
QUEUE7 clauses
The situation board shows, for the currently selected filter: the open backlog; the count of urgent tickets; the count in each deadline tier of QUEUE-002; the number resolved today; the mean first-response time of QUEUE-007 over the last 24 hours; the satisfaction score of FB-004 over the last 7 days; and, for each state of TKT-001, the count and the longest current wait. Every count is derived from the tickets the equivalent TKT-007 filter returns and opens that filter when selected. The board displays data no more than 30 seconds old together with the time of the data it is showing.
An active ticket's nearest unmet first-response or resolution deadline places it in exactly one tier: breached once the deadline has passed, at_risk when it is two hours or less away, due_soon when it is more than two and at most six hours away, and later beyond that. Remaining time is measured on the same basis as the deadline itself (QUEUE-003). Breached tickets are counted and displayed separately from at_risk.
Settings define one IANA timezone, weekly working hours, and dated holidays. Deadline calculations consume only configured business time unless the deadline policy chooses calendar time.
First-response and resolution targets are calculated from ticket priority and the business calendar. Policy: ticket.deadlines.v1; default first-response/resolution targets in business minutes are urgent 15/120, high 60/480, medium 240/1440, and low 480/2400.
Every active ticket shows the applicable target, calendar basis, absolute due time with timezone, and time remaining or breached; a recalculation records its reason and the previous due time.
Crossing a deadline marks the ticket breached and emits one notification event but never closes, resolves, or reassigns it.
A ticket's first-response time is recorded when its first operator reply is accepted; an acknowledgement under MAIL-016 is never a first response. Its resolution time is recorded when it first becomes resolved. Both are stored as elapsed business time and elapsed calendar time, are shown on the ticket, and are the values QUEUE-001 averages. Reopening never overwrites a recorded first-response time.
GUIDE4 clauses
Operators can create, edit, retire, and view versioned resolution policies; a retired version remains readable from historical tickets that cited it.
A resolution plan that relies on a policy identifies the exact policy version and the matching passage in the operator view.
Editing a policy affects only future analyses; stored plans retain the policy version and evidence originally used.
Operators can create, edit, and retire versioned internal knowledge articles with a title and body; AI may cite an active version, and historical citations continue to open the version originally used. Articles are never published to a customer-facing surface.
AI11 clauses
Each actionable new or reopened ticket queues an AI resolution plan, and an operator may refresh it. The plan appears within 30 seconds when the provider responds and contains a recommended next action, confidence, and evidence.
Every fact used by a plan, draft, or summary identifies its source type, source record, and retrieval time; operators can open the local source detail before acting.
A fact whose evidence is missing, older than the adapter's declared freshness window, or contradicted by another source is labelled unknown or conflicting. A plan in that state produces no recommended action, no draft asserting the fact, and no claim that an action succeeded.
When an operator requests a suggested reply, Brook prepares an editable reply in the composer; only an operator can submit it, and the outbound message records that AI supplied the draft and whether the operator edited it.
AI sets the ticket's issue type and a sentiment of positive, neutral, or negative within 30 seconds of an actionable customer message. It never overwrites an issue type an operator set, and an ungrounded classification remains unset rather than being replaced with a guessed default.
An operator can request a stored summary of a ticket. The summary records when it was produced, links to the messages and evidence it covers, and is shown alongside the ticket timeline, never in place of it.
AI cannot send mail, add a customer-visible message, change ticket or customer state, or invoke an external action. No policy or instruction may grant those effects.
AI calls go through the required ai.provider.v1 adapter. If a configured provider times out or fails at runtime, the affected plan, draft, triage, or summary shows the error, stores no partial result, and leaves email and human ticket handling available.
Every AI call records feature, provider, model, input and output units, latency, and estimated cost, whether it succeeded or failed; settings show totals by day and feature.
The plan-instructions policy selects buyer-supplied instructions for analyses and drafts but cannot widen the permitted data scope, change authorisation, or bypass AI-007. Policy: ai.plan-instructions.v1; default: use active resolution policies and require concise, evidence-backed, operator-reviewed output in the customer's language.
Each AI request captures the ticket revision and request sequence. A result for an older revision remains in history marked stale and never replaces the current result.
ACT12 clauses
When order or payment adapters are configured, Brook resolves customer and ticket references to orders, fulfilments, captures, refunds, and provider object IDs and displays the retrieval time.
An unavailable adapter or failed lookup is shown as unavailable with its last successful retrieval time; stale fixture data is never substituted.
A refund is the only external mutation Brook performs; every other adapter call is a read. A recommended refund is a proposal only and has no external effect until an operator confirms it.
A refund may be proposed only when the eligibility policy accepts the linked payment and amount. Policy: commerce.refund-eligibility.v1; default: the charge belongs to the customer, the amount is positive, and it does not exceed the provider-reported refundable balance.
Every refund requires an operator confirmation showing the adapter, operation, target provider object, amount and currency, customer, reply, and idempotency key. No policy or AI instruction may bypass the confirmation.
Brook durably records an action ledger entry before dispatch. Repeating a request with the same idempotency key returns the original entry and never creates a second external mutation.
A refund action is pending, succeeded, failed, or needs_review; the ticket shows provider references, attempt times, the approving operator, and the provider's latest response with card data and secrets redacted.
A timeout or ambiguous provider response becomes needs_review and is never retried automatically or replaced with a new refund.
An operator can reconcile a needs_review action against the provider by recording the verified provider outcome and reference; reconciliation updates the existing ledger entry and never dispatches a mutation.
When one confirmation includes a refund, success reply, and resolution, Brook dispatches the refund first. Only authoritative refund success permits the reply to be dispatched, and only provider acceptance of that reply permits the ticket to become resolved. Failure or needs_review stops later steps and displays completed and incomplete steps separately. Retrying reuses the original action and message idempotency keys.
A terminal refund failure leaves the ticket unresolved unless the operator separately resolves it, sends no success claim, and remains visible for correction.
A retryable transport failure known to have produced no provider effect may be retried at most three times with the original idempotency key; any ambiguity follows ACT-008.
NOTE6 clauses
Brook emails a notification for a new unassigned ticket, a direct assignment, a deadline breach, and an unattended customer follow-up, subject to the recipient's mute and quiet-hours settings. A new unassigned ticket goes to every operator; a direct assignment goes to the new assignee; a deadline breach and an unattended follow-up go to the ticket's followers (TKT-017), or to every operator when the ticket has none.
Repeated customer messages or deadline events for the same ticket produce at most one notification of each kind between operator open-or-acknowledge cycles.
Per-operator quiet hours defer that operator's non-urgent notifications until the next allowed time, without delaying intake, deadline calculation, or another operator's notification. Policy: ticket.quiet-hours.v1; default: urgent and breached tickets bypass quiet hours; other email notifications wait.
A notification delivery failure is retried under MAIL-007, becomes visible after terminal failure, and never blocks or rolls back the ticket event.
Operator notifications and acknowledgements use a configured no-reply sender that is not a support destination. Mail addressed to that return path is rejected and never creates or reopens a ticket.
When an actionable customer message remains unopened for five minutes, Brook queues one unattended notification to the ticket's followers, or to every unmuted operator when the ticket has none.
FB4 clauses
A resolution email includes a signed customer-feedback link when the feedback policy enables it. No survey is sent to a blocked customer, and reopening a ticket before its survey is sent cancels it. Policy: ticket.feedback.v1; default: include one survey with the first customer-visible resolution reply.
The feedback link discloses no ticket or customer detail beyond the business name, accepts one submission, and expires 30 days after it is sent.
A customer can submit a whole-number rating from one to five with an optional comment. The result is stored with its submission time, appears on the ticket and the customer profile, cannot be edited by an operator, and does not reopen the ticket or trigger AI.
A satisfaction score is the mean of the ratings received in the stated window, shown with the number of ratings it covers. A window with no ratings is shown as unavailable, never as zero.
DATA2 clauses
The portable export contains every ticket, message, attachment, customer, customer note, tag, resolution policy and version, internal article and version, AI analysis and usage record, feedback result, notification, setting, commerce reference and action-ledger entry, and audit record; it contains no secret.
An operator may export the currently filtered ticket list as CSV with ticket properties, deadline tier, and measured times; this convenience export does not replace the complete archive required by BASE-DATA-001.
Baseline guarantees
22 declared clauses from this product's BASELINE.md. Selected ownership guarantees are shown below; execution evidence is separate.
The deployment makes no request to the marketplace or the seed author. There is no telemetry, phone-home, licence check, or kill switch.
pnpm export produces every record and attachment; pnpm import restores it into an empty deployment; export, import, export yields an archive with equivalent contents.
User data leaves the deployment only through externals declared in seed.json → externals. No other outbound request carries user data.
Public surfaces (widgets, booking pages, forms, status pages) set no cookies, load nothing from third-party domains, and send no visitor data before the visitor's first interaction.
No secret appears in the client bundle, in logs, or in an error response.
Release history
Honest version: there is one release line so far, and we will not invent the rest.
- v0.2.0
- Current — Bounded operator-approved refund workflow verified locally (Workers/D1 and desktop/mobile browser); hosted provider sandbox and full retained contract acceptance pending.
The manifest states this version and maturity. Consult the source verification evidence before deployment.
DIY quickstart
The code is MIT and the deployment is yours. Nothing below talks to us; optional management remains planned.
Use the edition's source README for its available installation commands and required configuration.
View as agent
Everything on this page exists as machine-readable files. If you are an agent evaluating Brook for someone, read these instead of the prose.
Installation instructions are in the source README; this entry does not declare a catalogue install command.
- README.md
- Source setup, available commands and implementation status.
- seed.json
- The manifest: capabilities mapped to clauses, externals, extension points, limits, operating cost, deploy requirements.
- CONTRACT.md
- The 92 clauses above, in source form.
- BASELINE.md
- The product's declared baseline requirements.
- AGENTS.md
- Instructions written for the coding agent that will maintain this deployment.
- /llms.txt
- The site, for machines.
Catalogue endpoint
/agents/catalogue.json
Every seed with its id, category, version, clause count, install command and links to the files on the left. Generated from the seeds' own artifacts on every build, so it cannot drift from what ships.

Run Brook on your own cloud.
MIT code, your data, your infrastructure. Leave whenever you like and it keeps running.