Landscapt (CRM)

Tickets

Support and service tickets — where they come from, how they're worked, and how they connect to the rest of a client's record.

What a ticket is

A ticket (crm_tickets) is Landscapt's general-purpose customer-service record — a note, call, or event tied to a client that needs to be tracked, assigned, and resolved. Every ticket has a sequential ticket_number, a status, a priority, an optional category, and an optional client and assignee.

Tickets are shown as three different lists in the app — Tickets, Calls, and Events — but they're all the same underlying table and component (TicketsList), just filtered by type (note / call / event).

Not the same as CMMS Maintenance Requests

Tickets (this page) are Landscapt/CRM's customer-service record — a client asks something, reports something, or needs a callback. Maintenance Requests are a separate, module-specific Equipt/CMMS concept for reporting an asset or equipment problem that may become a Work Order. They live in different tables, have different fields, and are not interchangeable — a client complaint is a Ticket; a broken mower is a Maintenance Request.

Status, priority & type reference

These are literal string enums, not free text — the UI only ever writes one of these.

StatusValueMeaning
OpenopenDefault status on every new ticket — including every ticket a public form creates.
On Holdon_holdWaiting on something outside your control (a callback, a part, a client reply). Counts as the same “Open” stage on the status flow indicator.
PendingpendingBeing actively worked — the middle step on the status flow (“In Progress”).
ClosedclosedResolved. Sets closed_at and fires the Ticket Closed trigger (Zapier + internal automations).

The detail sheet's status flow indicator only shows three visual steps — Open, In Progress, Closed — because on_hold maps to the same step index as open. Under the hood it's still a distinct, filterable status.

PriorityNotes
LowShown with no badge in the list view — only High and Urgent get a colored pill.
NormalThe default on every new ticket, including form-created ones.
HighOrange badge in the list.
UrgentRed badge in the list.
TypeNotes
NoteThe default type — a written ticket. What Forms create when their Ticket Settings are on.
CallSame record shape, filtered into a dedicated Calls list (typeFilter="call") elsewhere in the app.
EventSame record shape, filtered into an Events list (typeFilter="event").

Category is a free-form org-configured list (crm_list_options under key ticket_categories), not a hard-coded enum. Every organization starts with a default list: Uncategorized, Estimate, Billing, Change Service, Schedule Service, Complaint, Damage / Warranty, Need to Contact Customer, Client Portal Message, Terminate Service, Other and Upsell (where crew-suggested work lands; it is never offered to clients in the portal). Rename, add or delete them under CRM Settings > Ticket Categories. (If a list is ever emptied, the pickers fall back to a short built-in set.) New damage cases file their ticket under Damage / Warranty.

Creating a ticket

Three ways a ticket comes into existence:

  1. Staff, manually — the Add Ticket (or Add Call / Add Event, depending on which list you're on) button opens NewTicketDialog, letting staff set type, client, category, subject, body, status, priority, assignee, and due date directly.
  2. A public form submission, automatically — every submission on a crm_forms form creates a ticket unless Ticket Settings is turned off on that form. The ticket starts as status: "open", type: "note", with the priority and category chosen in Ticket Settings (normal and the form's own name by default) and the body built from the submitter's name/email/phone/message plus every raw field on the form.
  3. A Communication Automation Event action — the Ticket action step lets an automation sequence create a ticket with a configurable title, description, priority (Low/Normal/High/Urgent), and assignee whenever it runs.

Bulk creation is also available via CSV import (Actions menu → Import), which requires only a Subject column.

Assignment

A ticket can be assigned to exactly one user at a time, via assigned_to (a display-name string) and assigned_to_id (a real crm_employees.id, resolved from the assignee-picker's selected employee). Both are set together whenever the UI's Assigned To dropdown is used — in the New Ticket dialog, the edit form, or the list's bulk Reassign action (select one or more rows → Actions → Reassign). A bulk reassign sends the new assignee one summary notice for the whole batch, not one per ticket.

Who can see tickets. Roles with View/Modify Tickets (and admins) see all of the company's tickets. Everyone else, crew logins included, sees only tickets they created or that are assigned to them — so a crew sees its own upsell suggestions but not the office's complaints or billing disputes.

resolveAssigneeId() in src/lib/ticket-notify.ts turns that crm_employees.id into a login user for notifications by looking up crm_employees.user_id. Tickets created before assigned_to_id existed, or created through a path that never had a real id (the public form path always leaves the assignee blank; the Zapier "Create Ticket" action has no assignee field either), only have the name string — resolveAssigneeId() falls back to fuzzy-matching that name against crm_employees so assignment notifications still work — but if the name doesn't match an employee exactly, assignment notifications are silently skipped rather than failing.

Worked example: a form-to-close lifecycle

  1. A visitor submits a contact form on your public site. submitFormResponse() matches or creates a client, then unconditionally inserts a crm_tickets row: open, normal priority, type note, category = the form's name, body = name/email/phone/ message plus every raw field.
  2. notifyStaffOfNewTicket() runs immediately — it notifies the org's configured New Ticket recipients (no assignee yet, so no one else is added).
  3. Because the ticket has a client, ticket_created fires for any Communication Automation sequence watching that trigger (optionally filtered to this form's category), and for any Zapier "New Ticket" trigger.
  4. A manager opens the ticket from the Tickets list, clicks Reassign (or edits the ticket directly) and assigns it to a rep. This sets both assigned_to and assigned_to_id, and fires a ticket_assigned notification to that rep alone.
  5. The rep works the ticket — adds internal comments (Comments & Messages tab, backed by CommentsSection), which notify the assignee if someone else comments, and sets status to Pending while they investigate.
  6. The rep resolves it and clicks Close Ticket (useCloseTicket). This sets status: "closed" and closed_at, then fires ticket_closed for both internal automations and Zapier.
  7. If the client later replies and staff change the status away from Closed, useUpdateTicket detects the prior status was closed and fires ticket_reopened.

Searching and filtering the list

The search box above the Tickets list has an × button, and Escape clears it. The column filter narrows by one field at a time; Category, Assignment (including Unassigned) and Priority are picked from a list and match exactly, instead of being typed. Created and Due Date are date-range filters (From / To). Created is compared on your company's calendar day, so a ticket logged in the evening lands on the day your staff saw it. The Calls list has the same Created and Due Date range filters.

The ticket detail sheet

Clicking any ticket row opens TicketDetailSheet, with five tabs:

  • Details — status flow buttons, the client/category/assignee/dates meta grid, body text, and a Linked Records picker that can attach the ticket to an Estimate, Invoice, Job, Project or Damage Case (estimates, invoices and jobs must belong to the same client).
  • Comments & Messages — two sections. Internal comments (staff only) use the shared CommentsSection (recordType="ticket"). Messages with the client is the conversation the client sees in their portal: their replies (with photos) and yours. A staff reply emails the client; a client reply reopens a closed ticket and notifies the assignee. The ticket must have Show in client portal on.
  • Files — attachments via the shared AttachmentsSection.
  • Contributors — additional staff CC'd on the ticket as participants, distinct from the single assignee. They get a notification when added and on every new comment (never for their own actions). Managed by use-ticket-contributors.ts.
  • Audit Trail — the shared AuditTrailTab change history: edits to the ticket itself (status, assignee, priority, client link, portal visibility, delete/restore) plus one-line events for the conversation — an internal comment added, edited or deleted, a client message received or sent, and an email sent (with the recipient). The message text itself is never copied here; read it on the Comments & Messages and Emails tabs.

Near the top of the Details tab, a Show in client portal switch controls whether the ticket appears in the client's portal. It's off by default (the same switch is on the New Ticket form), and it's disabled until a client is linked. When on, the client sees the subject, category, status and notes — never comments, files or who it's assigned to. Turn it off at any time to hide it again.

The ticket opens in a wide side panel. Under the subject, the status pill sits on the left and the PDF, Email, Edit and delete (trash) buttons sit together on the right edge. The tab bar (Details, Comments & Messages, Emails, Files, Contributors, Audit Trail) is sized to fit the panel without scrolling sideways on a normal desktop screen. PDF opens a print-formatted view in a new window — all ticket fields are HTML-escaped before being written into that window, since subject/body/names can originate from an unsanitized public form submission.

Field upsells from crews

When a crew spots work a property needs, they send it in from the crew app and it arrives here as an ordinary ticket with the category Upsell — so it inherits assignment, due dates, priority, comments, automations and the audit trail without being a separate thing to check.

The crew's side of this — where the Suggest work button lives and what they see — is in the Crew App guide. Each one carries the crew's note, who spotted it, the date, the property, and the service they suggested. Any photo they took is on the Files tab, which is usually enough to price the work without a trip out to look. The ticket is linked back to the job the crew was on.

Crews never see or set a price. They pick from a list you control and describe what they saw; pricing stays with the office. Open a service up for suggesting by ticking Show in field upsells on it under Services & Pricing, and give it a short prompt so crews know what to look for. With no services ticked, the Suggest work button doesn't appear in the crew app at all — that is how the feature is switched on and off for crews, with no permission to manage on their side.

To take one forward, click Create estimate on the ticket. That makes a draft estimate for the client, puts the suggested service on it at your catalog rate, links the two together and opens the estimate ready to price — so there's nothing to retype and nothing to remember to link afterwards. The button needs the Add Estimate permission, the same one every other way of creating an estimate requires, and it disappears once the ticket has been converted.

One ticket can only be converted once, even if two people click at the same moment or a conversion is interrupted half-way. The estimate itself claims the ticket, so a second attempt can't create a duplicate — it reopens the estimate that already exists, finishing any step the first attempt missed. If you convert a ticket by mistake, delete the estimate and the ticket becomes convertible again.

Re-quoting is still fine. You can link additional estimates by hand from Linked Records — useful if you quoted the work before the suggestion came in, or the client asked for a revised price. The reports read the most recent estimate linked to the ticket.

That link is what turns the suggestion into a number you can report on: the estimate carries the real sales pipeline (Sent → Accepted → Won/Lost), so a crew's suggestion reaching Won is measurable, while the ticket's own open/closed only ever means whether the office has dealt with it.

Two reports in the Report Center read this: Field Upsells lists every suggestion with its outcome, and Upsell Conversion by Crew Member totals what each person submitted against the revenue it won. Both are under the Service section, and each has its own permission so you can decide who sees them.

Automations, Zapier & notifications

Communication Automations can trigger on four ticket events:

TriggerFires when…
Ticket was createdFires when any ticket is created with a client attached — includes form-created tickets, since submit-form-response.ts links the ticket to the matched/created client.
Ticket was closedFires when a ticket's status changes to closed, from useCloseTicket or a bulk “Mark Closed” action.
Ticket was reopenedFires when an update changes status away from "closed" to anything else — i.e. the ticket's prior status was closed.
Ticket past dueNot a moment-it-happened event — evaluated against due_date via the ticket_past_due_days condition field (days since due date).

The same events are exposed to Zapier as instant triggers — New Ticket and Ticket Closed — and Zapier can also create tickets via the Create Ticket action (requires Client + Subject; accepts Body, Priority, Category, Type, and Due Date). See the Zapier guide for the full trigger/action reference.

Three notification preferences exist per user, each with separate email and in-app toggles:

PreferenceKeysWho receives it
New TicketinAppNewTicket / emailNewTicketSent to the org's configured “New Ticket Recipients” plus the resolved assignee (if any), excluding whoever created the ticket. Also fires for anonymous form submissions (no creator to exclude).
Ticket AssignedinAppTicketAssigned / emailTicketAssignedSent only to the newly-assigned user when the assignee actually changes (saving other edits doesn't re-send it) — skipped if they assigned it to themselves.
Ticket CommentinAppTicketComment / emailTicketCommentSent only to the resolved assignee — skipped if the assignee is the one commenting.

Good to know

  • Who can change tickets. Editing, closing, reassigning, deleting and restoring tickets need View/Modify Tickets, checked in the database. A crew login can still edit a ticket it created (such as a field upsell suggestion).
  • Email from a ticket. The document picker offers Client documents only. If the status change you chose doesn't happen because someone else changed the ticket first, no Ticket closed or reopened automation fires.

More in Landscapt (CRM)

Clients, Properties & Leads
Client accounts, commercial hierarchies, service properties, and the activity timeline that ties it all together.
Estimates & the Budget Engine
How an estimate is built, how its numbers are actually calculated, and how it becomes a job.
Jobs & Packages
The six job types, how a job's status differs from a visit's status, and how a Package template turns into a billed job.
Services & Pricing
The service catalog, bulk catalog price changes, and Price Adjustment runs — which prices seed new work and which ones actually bill.
The Dispatch Board
The daily scheduling screen crews and dispatchers live in — visits, crews, status, and how actual hours get calculated.
The Crew App
What a crew sees on their phone — the day's stops, clocking on and off, breaks, photos, and sending work back to the office.
Sales Meetings
Booking appointments per sales rep, the double-booking warning, and the split between a rep's direct reminder and the client-facing automation trigger.
The Waiting List
Jobs with a date range instead of a date, held for opportunistic scheduling — and how to actually get them dispatched.
Snow Jobs & Storm Dispatch
Storm events, priority dispatch, and the invoicing flow built specifically for snow.
Invoicing & Payments
How an invoice is born, how its status moves, and where the client's own PO number actually goes.
Contracts
Ongoing billing agreements — monthly amounts, seasonal overrides, sub-properties, and how signing and invoicing actually work.
Communication Automations
How sequences, triggers, and events work — and how Automations differ from Sales Campaigns.
Forms & Lead Capture
Building, publishing, and sharing public forms — and what happens to a submission once it lands.
Document & Email Templates
Building the reusable emails and text messages that go out with invoices, estimates, campaigns and automations — blocks, merge tags, buttons, previews and test sends.
Damage Cases
Tracking property damage and warranty claims tied to a job — what a case captures, how cost rolls up, and a real current limitation in how it connects to a client record.
Warranties
Tracking the warranties you give on walls, walkways, patios and renovations — when each starts and ends, how claims tie back to it, and expiry reminders.
Commissions
Paying sales reps a percentage of what they sell — plans and rules by service and sale type, how earnings are calculated, and locking a quarter into a statement.
Injury & Near-Miss Cases
Reporting injuries, illnesses and near misses from the field, following them up in the office, tracking self-paid costs, supervisor alerts, and the Accident Free Workdays KPI.
Job Photos
Field photo documentation, annotation, and before/after comparisons — attached to a job site, not a person.
Online Payments & Stripe
How a client actually pays an invoice online, how Stripe Connect keeps every org's money in their own account, and how the platform takes zero cut.
Browse all guides
© 2026 Landscapt. All rights reserved.