Seed catalogue · Support
Lily
A self-hostable ticket desk with numbered cases, shared views, Help Centre intake, configurable channels, service targets, customer records, and operational analytics.
- Alternative to
- Zendesk, under MIT
- Family
- Support
- Edition
- Lily
- Version
- 0.1.0
- Contract
- 109 clauses across 19 areas
- Maturity
- thin-real-worker-d1-ui-port; ticket-desk-contract-draft






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
Runs on your cloud's free tier
The seed declares the free plan as the default deployment. Its own estimate, thresholds and sources are below, unedited.
- Status
- free-plan default; production usage not measured
- Estimate
- The locally deployable core uses Cloudflare Workers, D1, R2, Queues, and Cron. Mail, record-link, commerce, and AI provider costs exist only when a buyer enables those adapters and are paid directly by that buyer. Published cost and capacity claims await Stage 2 measurement.
- Included
- Cloudflare Worker, D1, R2, Queues, Cron
- Excluded
- mail provider, record-link provider, voice provider, optional commerce provider, optional AI provider
Externals declared
8 externals, each with a reason
Under BASE-DATA-004, user data leaves only through what is declared here.
- native-auth-mail
- Cloudflare primitives do not originate transactional email; Lily uses the explicitly selected Resend or SendGrid transport to deliver native magic-link sign-in messages.
- mail-provider
- A configured provider supplies inbound delivery callbacks, outbound email transport, and authoritative delivery reports.
- record-link-provider
- A buyer-controlled adapter may expose named external records without making Lily an owned CRM or commerce system.
- commerce-and-ai-provider
- The retained optional specification requires replaceable commerce and AI providers while keeping manual core ticket work independent.
- help-centre-ai-provider
- Article-grounded generated visitor answers require a buyer-selected model provider.
- voice-provider
- A buyer-selected telephony service supplies phone lines, queue state, and call records that Cloudflare primitives do not originate.
- external-sync-provider
- A buyer-controlled provider transport is required only when that extension synchronises records outside Lily.
- sendgrid
- Optional explicitly selected provider for native sign-in, signed inbound mail, human-reviewed public replies, and signed delivery events. Provider acceptance is not delivery confirmation.
Extension points
42 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
- estimated
- Basis
- Capacity and rate values are not load tested and must be replaced by measured limits before the first release. Public request values are per-IP one-minute enforcement thresholds, not capacity proof. Attachment content types are a product decision, not a measurement.
- Concurrent Operators
- 25
- Tickets
- 50,000
- Messages
- 250,000
- Inbound Messages Per Minute
- 20
- Outbound Messages Per Minute
- 20
- Active Automation Rules
- 20
- Attachment Bytes Per File
- 10,485,760
- Attachment Bytes Total
- 10,737,418,240
- Attachment Content Types
- image/png, image/jpeg, image/gif, image/webp, application/pdf, text/plain, text/csv, message/rfc822, application/zip, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- Public Requests Per Minute Per Ip
- Assets 240
- Health 60
- Landing 60
- Sign In 10
- Survey 30
- Provider Webhooks 120
- Integrations 120
- Help Centre 60
- Help Request 20
- Widget 30
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 businesses or tenants
- A separately browsable conversation inventory
- An owned CRM, commerce, order, payment, fulfilment, or marketing system
- Zendesk community, badges, moderation, theme marketplace, dashboard sharing or restrictions, CRM suite/tasks/prospecting, native mobile SDK, or social connectors
- Application marketplace, classic report builder, vendor benchmark survey, product switcher, signup/trial/billing chrome, or workforce management
- Outbound campaigns, arbitrary external webhooks, native mobile applications, or voice recording
- Autonomous customer messages, ticket lifecycle changes, external mutations, or fabricated adapter data
- Freshdesk replacement or import until compliant mapped public-source evidence exists
The contract
All 109 clauses of Lily, 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 business name, a short configured description of its support service, links to the public Help Centre and configured public request surfaces, and a sign-in link to /signin. It contains no vendor pricing, trial, billing, product-switcher, or marketing claims.
APP1 clause
An authorised user entering /app can reach Home, Views, Tickets, Customers, Help Centre, Analytics, Channels, and Administration according to their role. Navigation has a compact responsive form, global search, keyboard shortcuts for search/navigation/common ticket actions, contextual coach marks, and a dismissible setup checklist whose completed state is per Administrator. An unavailable optional channel or adapter is labelled unavailable with the configuration action rather than simulated data.
ACCESS4 clauses
Lily has Administrator, Agent, Contributor, and Light Agent roles. Administrators manage deployment-wide settings, people, groups, forms, roles, channels, and policies. Agents work tickets and public replies. Contributors can make internal contributions only. Light Agents have the explicitly configured limited ticket access. An authenticated person may perform only actions permitted by their role and any ticket/group restriction.
Administrators create, rename, retire, and populate groups. A ticket can be assigned to a group and then an agent; group assignment is visible in views, history, filters, reports, and exports. Retiring a group requires an explicit reassignment or unassignment of its open tickets and retains historical attribution.
Administrators invite, suspend, restore, and remove agents. Removing an agent unassigns their active tickets and preserves the author identity on earlier messages, notes, and audit records. The last active Administrator cannot be removed. Agents configure their own signature and notification preferences; the configured signature is appended only to their public email reply.
Each case mutation supplies the revision it was based on. A stale mutation returns 409 and makes no partial field, status, note, assignment, automation, or outbound-message change. An accepted inbound message is durable even if it races with an agent edit.
TKT18 clauses
A human customer email to the support address or an agent's manual submission creates one ticket with an immutable ID, creation time, customer, channel, subject, initial message, declared ticket custom fields, and revision. A new ticket is Open with Normal priority unless the creating agent chooses another priority.
A ticket is Open, Pending, Solved, Closed, or Merged. Pending carries exactly one configured reason, and every reason declares whether it waits on the customer; a ticket is never Pending without a reason. Solved records an agent-confirmed outcome, Closed is immutable archival history, and Merged is an immutable redirect under TKT-016.
A qualifying human customer message on an Open, Pending, or Solved ticket appends to that ticket, makes it Open, and clears its pending reason. A message matching Closed creates a linked new ticket; a message matching Merged appends to the destination under TKT-016. No qualifying human customer message is discarded.
A Solved ticket closes seven days after it was last solved unless a qualifying human customer message arrives first. Closing never deletes messages, evidence, executions, or audit history. Policy: ticket.auto-close.v1; default: seven days.
Agents can filter tickets by status, pending reason, priority, assignee, group, tag, channel, service-target state, and ticket custom fields; sort by target due time, last activity, or priority; and save a named view that authorised agents can open. My queue lists Open and Pending tickets assigned to the current agent, earliest target due time first.
Full-text search over ticket IDs, subjects, message bodies, customer names and email addresses, tags, and configured custom fields returns matching authorised tickets within 500ms at the stated limits.
New tickets are assigned by the assignment policy. Agents may assign or reassign a ticket to one agent, one group, or both without changing state or service clocks. Policy: ticket.assignment.v1; default: unassigned.
Every ticket has Urgent, High, Normal, or Low priority. Authorised agents and allowed automation may change it; each change records actor, prior value, new value, and time.
The ticket timeline presents customer messages, public replies, internal notes, system events, adapter evidence, automation runs, and execution records in durable event-time order, with insertion order as a stable tie-breaker across reload and pagination.
Public replies are eligible for customer delivery. Internal notes are a distinct stored kind that cannot be passed to a delivery adapter, included in customer-facing export, or used to claim an action completed.
Solving is an explicit agent action and sends no draft or external action. A Solved ticket rejects new public replies and external executions until it is reopened under TKT-003 or TKT-015; Closed and Merged tickets reject them permanently.
Each ticket mutation supplies its revision. A stale mutation is rejected with the current revision and makes no partial change; inbound message append never fails because of a concurrent agent edit.
An agent may merge only tickets for the same customer, neither Closed nor Merged, after confirming source and destination IDs and a reason. The source becomes Merged and stores the destination ID. Cross-customer merge is refused; it needs a separately versioned, elevated extension with explicit participant and recipient reconciliation before customer-visible data could cross a customer boundary.
An agent-created ticket records whether its initial message came from the requester or agent, and that attribution is immutable. Agent-originated tickets start no first-response clock and are excluded from first-response reporting.
An agent may reopen Solved to Open with a required reason; it starts a new service cycle, sends nothing, and records the action. Closed and Merged tickets cannot be reopened.
A Merged ticket is read-only, its timeline is presented on the destination, replies addressed to stored threading identifiers reach the destination, and both IDs remain resolvable in search and audit history. Its source conversation stays linked to the immutable source and is transcluded on the destination with original conversation and author identity. Source and destination retain independent lifecycle and completion histories; routing or transclusion changes neither lifecycle.
Agents apply and remove case-insensitive canonical tags on tickets and customers; each change records actor and time. Tags filter queues, match search, serve as automation conditions and report breakdowns, and appear in export. Removing a tag retains the audit event.
Sending a public reply leaves the ticket Pending with a configured reason that waits on the customer, unless the agent selects a different permitted status before sending. Sending never solves a ticket alone. Policy: ticket.reply-status.v1; default: Pending, waiting on the customer.
CASE10 clauses
A Lily case is the visible numbered ticket. Its public number is unique for the deployment life, never reused after deletion or merge, and remains resolvable after import. Each new ticket creates one hidden conversation by default. Customer-visible messages live on that conversation; notes, evidence, assignment, actions, and lifecycle live on the case. Completion never closes or deletes the conversation.
A saved view has an All block and an optional Any block. It matches when every All condition matches and, when Any is non-empty, at least one Any condition matches; an empty All block is true and an empty Any block imposes no additional condition. Rows, counts, exports, dashboard drill-through, sorting, pagination, and selected columns use the same stored predicate.
Administrators define ticket forms, required fields, and custom fields; creation preserves the selected form and submitted values. An authorised agent can add/remove followers, who receive ticket.followers.v1 updates without needing to be owner or assignee. Detail exposes requester context, selected columns, filtered activity types, and configured tabs. Policy: ticket.followers.v1; default: assignment, requester message, note, public reply, at-risk, breach, and solved events notify followers subject to preferences.
An authorised agent can apply reviewed bulk assignment, tag, status, macro, or spam actions to a filtered selection with independent per-ticket success, skip, or failure outcomes. Spam marking, requester suspension, deletion, and restoration require the separately authorised destructive action and preserve audit history under baseline retention.
Home presents assigned/followed work, capacity, activity, and queue links; availability influences only later routing. The responsive mobile workspace supports ticket work and each agent can configure mobile notification categories and quiet hours with the same routing and suppression semantics as NOTIFY-001 and NOTIFY-002.
A configured widget may keep a live visitor/agent conversation. Each live conversation has a visible participant and queue state, is linked to its ticket when a request is submitted or an agent creates one, and retains its message chronology when it becomes the ticket's hidden conversation. An agent presence indicator never claims a live responder when none is assigned.
Global search returns authorised tickets, customers, organisations, Help Centre articles, agents, groups, and saved views, identifies the matched type and field, and keeps a per-agent recent-record list that can be cleared. Ticket full-text latency remains TKT-006; global results preserve access checks and do not reveal a recent record after access changes.
Agents can open concurrent ticket tabs without losing unsaved drafts, return to a chosen tab, and see its current revision or conflict state. A queue can offer an explicit guided next-ticket action based on its visible ordering; its action, selection rationale, and any skipped ticket are recorded. The exact guided-play interaction is a Lily decision because the available Zendesk capture establishes the surface but not its execution.
Ticket tags display as removable chips, offer existing canonical tags while typing, and permit an authorised agent to create a new tag inline. Each entry/removal is audited. Autocomplete and inline creation are Lily product requirements; the cited Zendesk evidence establishes display and removal only.
The composer persists rich formatting, emoji, links, and accepted attachments before submit. Workspace chrome shows authorised agents live conversation and assigned-work counters. Administrators set an enforced per-agent active-ticket cap; routing skips capped agents and records the reason, while direct assignment above the cap requires an explicit authorised override.
MAIL14 clauses
A signature-verified provider delivery to a configured support address creates or appends a ticket message within 60 seconds, preserving sender, recipients, subject, sanitised text and HTML, attachments, provider ID, and threading headers.
An inbound email appends only when In-Reply-To or References matches a stored message ID, or a valid Lily-issued opaque reply token identifies its ticket, and its sender is the current requester or current participant. The timeline audits which identifier matched. A removed or unknown sender starts a new ticket even with a valid identifier; sender and subject alone never join mail.
An unmatched message starts a new ticket. Conflicting headers, or a valid token that disagrees with them, start a new ticket with a visible conflict event naming both candidate tickets; the message is never attached speculatively.
Replaying one provider event with the same provider message ID produces one stored message, one service-cycle effect, and at most one routing, acknowledgement, and automation effect. Provider-event ID and RFC Message-ID are durably one-to-one. If either known identity arrives paired with a new, conflicting identity or immutable thread metadata, Lily quarantines the conflict without routing, service-cycle, acknowledgement, or automation effect until reviewed. Body equality never resolves it.
Mail with automatic, bulk, delivery-status, receipt, or no-reply signals is non-actionable. A matching item is stored as a visible system event and does not reopen, notify, start a service cycle, run automation, or request an acknowledgement. An unmatched item creates no queue entry and is retained as a discarded-mail audit record.
A public reply atomically creates one durable outgoing message and one dispatch job with a stable idempotency key, reply headers, and a Lily-issued opaque reply token, then dispatches that job within 60 seconds. It uses the ticket's stored reply mailbox: the first configured support-address match retained from RFC intake under MAIL-011, or, for a manual, web-form, widget, or other non-RFC ticket, exactly one enabled configured support mailbox selected and stored by an authorised agent before its first public reply. A repeated key returns the original message and dispatch job.
Each outgoing message monotonically progresses through queued, accepted, delayed, delivered, bounced, rejected, suppressed, failed, or needs_review. Provider acceptance is never customer delivery. There are at most five total dispatch attempts in one hour with the original key. A failure before provider acceptance never stops a first-response clock. Lily preserves provider-acceptance time and delivery evidence. If the sole qualifying public reply later reaches final bounced, rejected, or failed state, it restores that service cycle's original first-response clock and target due calculation without resetting elapsed time; if another qualifying public reply remains provider-accepted, the clock stays stopped. The pre-send ticket status and its mutation revision are recorded; on that same sole-reply final failure, Lily restores the pre-send status only when no later independent status mutation occurred, never clobbering later agent changes. Final failure or needs_review notifies under NOTIFY-004. An ambiguous needs_review outcome is never automatically retried and requires provider reconciliation.
Verified provider reports move an accepted message only forward to delivered, delayed, bounced, or rejected and record report time. Duplicate or out-of-order reports never regress a terminal state; Lily never labels delivery without an authoritative report.
Inbound and outbound attachments remain bound to their immutable message. A file outside the seed limits is rejected with filename and reason while the readable message and accepted files are retained.
Intake and delivery webhooks are rejected without a valid mail.receiver.v1 signature. Duplicate and out-of-order delivery events are tolerated and never create data or regress a terminal state.
One RFC message addressed to multiple configured support addresses creates one intake record, retains every matched support address, and records the first configured match as its reply mailbox. A new ticket records the sender as requester and only non-support To and Cc recipients as participants; Bcc is never disclosed. A public reply addresses the requester and current participants the agent keeps in the composer; removed recipients are not restored later. Support and notification addresses are never participants.
Lily acknowledges a verified intake only after a durable intake record exists; failure before it returns 503 with no partial ticket. Post-record parse, storage, or enqueue failure is visible with a retryable or terminal reason. A verified message with no configured support destination is quarantined with destination and reason.
An agent-authored public reply uses the ticket's stored reply mailbox: the first configured RFC support-address match under MAIL-011, or the authorised enabled configured-mailbox selection stored under MAIL-006 for a manual, web-form, widget, or other non-RFC ticket. It carries ticket threading data and appends that agent's configured signature. Operator notifications use a no-reply address that is not a support destination and carry no agent signature. Automatic acknowledgement is not a notification and follows MAIL-014.
When enabled, the actionable message that creates a ticket produces exactly one stored automatic acknowledgement under MAIL-006 through MAIL-008. It uses the ticket's stored reply mailbox and threading data but no agent signature; it is not sent from the no-reply notification address. No acknowledgement is sent for manual, non-actionable, blocked, or later ticket messages, and it never counts as a first response. Policy: channel.autoresponder.v1; default: disabled until an Administrator enables it for a mailbox.
CHAN6 clauses
Administrators see every configured intake and delivery channel, its enabled state, form or mailbox binding, group routing, public address, and configuration health. Disabling a channel preserves its history and stops only later intake or dispatch through that channel.
A configured public widget collects a request with the configured form and creates a ticket under TKT-001. It identifies the current requester where consented, exposes the required privacy text, and confirms a successful request without pretending an agent is present. An Administrator can use a simulator to preview configured form, bot, appearance, proactive-message, and live-conversation states without creating a real ticket or sending a visitor message.
A configured bot greets the visitor, offers up to ten article-grounded generated answers from approved Help Centre material, and shows cited article links. It stores every bot and visitor turn before agent handoff in the linked conversation, collects the ticket form, and hands off for no valid answer or visitor request. Core ai.provider.v1 receives only approved article material and disclosed visitor input, never private notes; cited identifiers are validated against supplied articles and invalid output fails for review rather than falling back to invented copy.
An administrator can configure an eligible widget audience and a reviewed proactive message. The configuration states its trigger and frequency limit, respects consent and suppression, and records each presentation; it does not create a ticket unless the visitor submits a request.
A configured voice adapter exposes queues or lines with waiting-call count, agent or group routing context, call state, and stable Ticket ID when one exists. An authorised agent can attach a call record to an existing ticket; the attachment is audited and adds no invented call-to-ticket relation when no ticket was selected. This is a Lily lifecycle decision; the Zendesk evidence only establishes a Live Calls Ticket ID column.
An authorised agent can open a labelled side conversation with an approved external participant, see its delivery state and replies on the ticket, and keep it separate from the customer-visible conversation. It cannot disclose private notes without an explicit reviewed message.
CUST7 clauses
The first qualifying customer message from an unseen normalised email address creates one customer; comparison is case-insensitive and a shared display name never merges identities.
A profile has display name, primary and observed email addresses, locale, tags, declared custom fields, and private account notes. Locally owned fields are editable and adapter-sourced fields are read-only with source and refresh time.
A profile shows every Open, Pending, Solved, Closed, and Merged ticket for that customer with its satisfaction and available adapter records.
Agents may add private, attributed customer notes. Notes are never sent to the customer or written back through an adapter.
An agent merges customers only after confirming survivor and source IDs and seeing addresses, ticket counts, and record links. Tickets, addresses, notes, tags, fields, and audit history move to the survivor; incompatible provider customer IDs block the operation and name the conflict.
Erasing a customer removes locally owned messages, attachments, notes, evidence snapshots, and tickets under BASE-DATA-003; it never claims to erase a provider account or provider data.
This predecessor guarantee applies only to an enabled external.sync.v1 extension described in research/optional-commerce-adapter.md: erasure stores a one-way keyed digest of normalised provider namespace and resource ID, no customer field, to prevent automatic resync recreation; it travels with export/import and only explicit restore removes it. Core Lily has no automatic external sync or sync credential.
CUSTOMER2 clauses
Administrators create organisations and membership. An authorised agent may suspend a requester; Policy: requester.suspension.v1; default: new intake is quarantined for review while earlier history stays readable.
An Administrator can run a reviewed CSV customer import with mapping, validation, duplicate handling, and per-row outcomes. An optional record-link adapter attaches named external records with adapter, identifier, link state, and retrieval time; unavailable data is labelled unavailable and never fabricated.
HELP5 clauses
An Administrator activates or deactivates the public Help Centre; deactivation makes public article and request paths unavailable while preserving authoring history. Published content is visible only to its named audience. An audience selects signed-in users, staff, groups, tags, and users with All/Any blocks: all All conditions and, when non-empty, one Any condition must match; empty All is true and empty Any adds no restriction. Administrators preview matching users and anonymous/staff roles. A preview is not evidence of signed-out deployment reachability.
Agents with article authority can create, edit, publish, unpublish, archive, and restore articles in categories and sections. Articles have a title, body, author, timestamps, lifecycle state, audience visibility, content tags, and version history. Article history is filterable by actor, event, and date. A selected article set supports reviewed bulk publish, unpublish, archive, restore, category, and audience changes with per-item success, skip, or failure results.
The Help Centre offers text search, category navigation, pagination, article metadata, a shared media library with upload, search, insert, and removal permissions, and article comments when comments are enabled. Search results respect audience visibility and label unavailable translations or attachments rather than substituting a different article.
Administrators configure Help Centre languages, translated article variants, branding, theme appearance, and a reviewed public preview. A language fallback is explicit and never represents untranslated copy as a translation.
A public request form uses an administrator-selected ticket form, validates required fields, creates a ticket under TKT-001, and gives the requester a result that does not expose another ticket. Its submission is distinguishable from article comments and widget intake.
ADMIN2 clauses
Administrators configure business hours, IANA timezone, holiday calendar, ticket types and priorities, statuses, forms, custom fields, canned replies, mailbox identities, routing, authentication, retention, and API/webhook credentials. Retention configuration can label, report, or schedule review of records but cannot automatically delete user data; deletion follows the applicable baseline clause and explicit authorised action. The settings screen distinguishes active configuration from unavailable optional adapters.
Administrators can inspect routing rules, capacity limits, API/webhook integrations, installed approved applications, their last execution or error, and current storage/API/channel usage meters with source, period, and refresh time. No setting silently expands authorisation or gives an external application access beyond its configured scope.
MACRO3 clauses
Authorised agents create, edit, archive, and search shared macros for a public reply or internal note. A macro may propose tags, priority, assignment, form fields, or status, but does not itself send a message. The library reports applied count, recent use, and sortable rolling seven-day usage without exposing ticket content to an unauthorised viewer.
Applying a macro resolves its declared placeholders from the current ticket, requester, organisation, and agent. An unresolved required placeholder blocks use and identifies the missing value.
A macro fills an editable draft and previews every field change. Submitting remains an explicit TKT-018 and MAIL-006 action and cannot bypass revision, authorisation, or outbound delivery rules.
SLA6 clauses
Administrators configure first-response and resolution targets against one workspace timezone, working-hours schedule, and holiday calendar. Policy: service-target.selection.v1; default: targets are selected by ticket priority and measured in business time. A ticket with no matching target displays that fact and is excluded from met/breached denominators.
Starting a service cycle stores the target version, duration, due time, and business-calendar inputs. Later setting edits do not rewrite its result. Recalculation records its reason and prior due time.
The first-response clock stops only when the first public agent reply is provider-accepted, subject to MAIL-007's sole-qualifying-reply final-failure restoration. Assignment, notes, macros, automation, an acknowledgement, and queued or failed delivery do not stop it.
The resolution clock runs for Open, and for Pending unless the selected pending reason explicitly waits on the requester. It stops when the case is solved. A requester message or agent reopen starts the next cycle while retaining prior cycles.
Ticket rows, ticket detail, Home, and saved views show the exact due time, calendar basis, remaining or overdue time, and state active, paused, at_risk, or breached. At risk begins at 25% of target duration remaining in business time.
Crossing a target marks the cycle breached, emits a notification event, and never resolves, closes, reassigns, or otherwise mutates the ticket on its own.
QUEUE5 clauses
Home shows the current selected view's open backlog, urgent count, target-state counts, resolved-today count, mean first-response time over the last 24 hours, and satisfaction score over the last 7 days. For every ticket state it shows the count and longest current wait. Every count opens the equivalent saved-view conditions and shows the data timestamp; it is no more than 30 seconds old at the stated limits.
An active ticket with a target is exactly one of breached once past due, at_risk with 25% or less of its target duration remaining, due_soon with more than 25% remaining and two business hours or less to due, or later otherwise. A paused cycle is paused; a ticket with no matching target is no_target. The four active-target tiers are mutually exclusive and exhaustive.
The default work ordering is breached, at_risk, due_soon, later, paused, no_target, then nearest active due time, priority, latest requester activity, and ticket number. An agent may choose documented alternatives without changing the selected view conditions.
A queue and ticket disclose which target is due, absolute due time with timezone, and the previous/current due time and reason for a recalculation.
First-response and resolution measures are stored as business and calendar elapsed time and shown with their cycle. Reopening never overwrites a recorded first response.
AUTO7 clauses
A rule is Draft, Active, or Paused and has a name, ordered conditions and actions, creator, latest editor, version, and run count. Draft and Paused rules process no new events.
A rule can react to ticket creation, requester message, ticket field or status change, or one scheduled elapsed-time condition. It may apply a tag, set priority, assign an eligible group or agent, set a pending reason, send an internal notification, or apply a macro draft. It cannot send a public reply, complete a ticket, change a requester record, or invoke an external mutation. Policy: automation.action-allowlist.v1; default: the listed actions only.
Active rules evaluate in visible order against one immutable triggering revision. Each run records matching conditions and each applied, skipped, or failed action.
A rule run is unique by rule version, ticket, triggering event or scheduled window, and action. An evaluation chain evaluates each active rule version at most once; duplicate events and scheduler runs repeat no effects.
A rule rechecks its conditions and ticket revision before mutation. A conflict is a visible skipped run. A failed action is visible with rule, ticket, attempted action, and error; a retry reuses its idempotency key.
Only an Administrator may activate, pause, or change a rule's owner-visible scope. A rule cannot act on a ticket, group, form, or field its owner is not authorised to configure.
A retried failed rule run uses the original triggering revision and idempotency keys, reports any now-stale condition, and never replays an action that already succeeded.
NOTIFY4 clauses
New unassigned tickets notify every eligible Agent; direct assignment notifies the new assignee or every eligible member of the assigned group; self-assignment sends nothing. Requester follow-up, at-risk, breach, and solved notifications go to followers, or to the assignee then eligible Administrators if there are no followers. An at-risk and a breach notification occur at most once each per service cycle.
Repeated requester-follow-up events coalesce to one notification until an assigned agent opens the ticket; repeated event categories use their named service-cycle or open-cycle boundary. Policy: ticket.quiet-hours.v1; default: quiet hours defer non-urgent notifications while urgent and breached work bypasses them. Intake, service clocks, and other recipients are unaffected.
Notifications use a configured no-reply sender that is not a support destination. Mail to that address creates or reopens no ticket and triggers no automation. Automatic acknowledgements are ticket mail under MAIL-014, not notifications.
A public-message delivery failure, automation failure, notification failure, or configuration/security failure is visible on the ticket or settings record and is delivered to the initiating agent, or every active Administrator when there is no initiating agent. This responsible-failure notification cannot be muted and never blocks or rolls back its causing event.
CSAT3 clauses
A solved ticket with a customer-visible accepted reply can queue one signed satisfaction request for that solve cycle. Policy: csat.request.v1; default: one email 24 hours after the solve. No survey is sent to a blocked requester, and reopening cancels an unsent request.
The single-use public link accepts one whole-number rating from one to five with an optional comment, expires after 30 days, exposes no unrelated ticket data, and never creates or reopens a ticket.
A result shows on the ticket and customer record with its solve cycle and time. A later cycle makes a new request and never overwrites a prior result.
INSIGHT8 clauses
Analytics provides prebuilt dashboards for ticket volume and status, first-response and resolution duration, service-target outcomes, satisfaction score and response rate, assignment and group workload, and channel mix.
Every report states its period, timezone, selected view or filters, ticket states, denominator, freshness time, and comparison period. It can filter by assignee, group, type, priority, tag, form, channel, custom field, and target state.
An authorised user can create, edit, save, share, and retire a report using documented ticket, customer, satisfaction, and service-target fields. A report exposes the tickets behind an aggregate only to viewers authorised for those tickets.
Dashboard and report users can choose supported chart and table formatting. An export contains the complete durable result for current filters and period with metric definitions, rather than only the rendered page.
Ticket, service-target, and satisfaction analytics reflect committed mutations within five minutes at the stated limits. A panel shows its last successful refresh and a stale or failed state; no cached result is represented as current.
An agent can export the complete durable current result of an authorised view, queue, dashboard, or report, including selected filters, period, and metric definitions. This convenience export does not replace DATA-001.
A merged source remains auditable and searchable but is excluded from live backlog, created, solved, and service-target totals. Its source conversation entries remain visible through canonical transclusion without double-counting the canonical case.
Each classified volume, satisfaction, or service-target aggregate exposes its contributing tag or configured classification, stated denominator, and authorised ticket drill-down. Unclassified eligible tickets appear as Other; the displayed drivers, Other, and excluded denominator reconcile to the reported total.
DATA3 clauses
Lily's complete export includes tickets, cases, conversations, messages, attachments, customers, organisations, forms, tags, views, macros, service cycles, automations and runs, satisfaction records, settings, article versions, record links, notifications, and audit entries, with original attachments. It contains no secret and follows baseline data guarantees.
Export, retention, deletion, and erasure identify locally owned data and optional-adapter references. Lily does not claim to update, delete, export, or erase a provider record unless the configured adapter returned a confirmed result for that operation.
An Administrator can use the documented importer with an administrator-provided Zendesk export into an empty Lily installation. It validates mappings before mutation and offers dry run. It imports numbered tickets, requesters, messages, original attachments, tags, forms, groups, users, status history, merge history, and available metadata, with a per-record outcome and one import audit record. Each source object has a preserved source ID and a Lily ID; source ticket numbers are retained only when unused, otherwise Lily allocates the next permanent number and records the source-number mapping. Existing source IDs make reruns idempotent. Imported merge and status history are retained as source history; unsupported data receives a per-record outcome without fabrication. Stage 2 must prove this with a real export; no Freshdesk importer is committed.
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 run export produces every record and attachment; pnpm run 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.1.0
- Current — thin-real-worker-d1-ui-port; ticket-desk-contract-draft
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 Lily 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 109 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 Lily on your own cloud.
MIT code, your data, your infrastructure. Leave whenever you like and it keeps running.