Best viewed on a desktop - the flow diagrams are wide. On mobile, swipe a diagram to explore it.
BrewedOps
Reusable support-desk blueprint

An automated support desk, built inside GoHighLevel

A customer submits a support form. An opportunity opens in a dedicated Support pipeline and becomes the ticket. Every stage has its own automation, so dragging a card to a stage fires exactly the right actions and the right customer email. Drop your brand, your categories, and your team in - the engine doesn't change.

0Pipeline
0Stages
0Emails
0Workflows
0Manual replies
Built inside
The flow

How it works

A customer submits the Submit a Support Ticket form. That creates or matches a contact and fires the New sequence, which generates a ticket number, opens an opportunity in the Support pipeline, sends the received email, and tags the ticket by category. The opportunity is the ticket - its stage is the status, and all ticket data lives on opportunity fields so every ticket is self-contained. Each stage runs its own automation the moment the card lands there.

flowchart TD F["FORM: Submit a Support Ticket
name, email, category, type, issue, link, file"]:::form F -->|creates / matches contact| S0 subgraph NEWSEQ["NEW sequence"] direction TB A2["ticket ID + open opportunity
copy fields contact -> opportunity"]:::act E1["Email 1 - Ticket Received"]:::mail RT["Category router: tag + notify support team"]:::route end S0["NEW"]:::snew --> NEWSEQ S0 -->|priority drag| S1["URGENT"]:::surg S1 --> USQ["Urgent sequence:
priority alert email + SMS, tag urgent"]:::act S0 --> S2["IN PROGRESS"]:::sprog S1 --> S2 S2 --> IPQ["In Progress sequence:
assign owner task, clear flags"]:::act S2 -->|fill Info Needed, drag| S3["AWAITING REPLY"]:::swait S3 --> AWQ["Awaiting Reply sequence:
Email 2, remind 3d, auto-close 5d"]:::mail AWQ -.-> S4 S2 -->|fill Resolution, drag| S4["RESOLVED"]:::sdone S4 --> RSQ["Resolved sequence:
stamp date, Email 3, optional CSAT"]:::mail CR["Customer replies (any time)"]:::form --> W4["Reply handler:
reactivate / reopen -> In Progress"]:::wf W4 --> S2 classDef form fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F classDef wf fill:#0B1E3F,stroke:#0B1E3F,color:#FFFFFF classDef act fill:#FFFFFF,stroke:#FF7A1A,color:#0B1E3F classDef route fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F,stroke-dasharray:4 3 classDef mail fill:#FFF4EC,stroke:#FFA155,color:#0B1E3F classDef snew fill:#EEF1F6,stroke:#5A6479,color:#0B1E3F classDef surg fill:#FBEAE8,stroke:#D2544A,color:#0B1E3F classDef sprog fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F classDef swait fill:#FAF3E4,stroke:#C98A2E,color:#0B1E3F classDef sdone fill:#EAF4EE,stroke:#3FA66A,color:#0B1E3F
The core idea

The opportunity is the ticket

The form writes to contact fields as a capture layer. The New sequence copies them onto opportunity fields, where the real ticket record lives - so a customer who opens a second ticket never overwrites the first.

Gap closed
Per-ticket isolation. If ticket data lived on the contact, a customer opening a 2nd ticket would overwrite the 1st ticket's number, issue, and resolution (GHL matches contacts by email), and the older ticket's emails would reference the newer ticket's data. Putting ticket data on the opportunity makes every ticket independent. The contact fields are only a transient capture layer the New sequence copies onto the opportunity.
Step 1 · Pipeline & stages

One pipeline, five stages

Create a pipeline named Support Tickets with five stages, in order. The stage is the ticket status, and each stage has its own automation that fires the instant a card lands there.

StageMeaningIts automation (on entry)
NewJust arrived (set on form submit)Ticket ID + open opportunity + copy fields + Email 1 + category router
UrgentManual priority flagTag priority-urgent + alert (email + SMS) to the team. No customer email.
In ProgressTeam actively working itAssign owner task + clear stale flags. Optional "we're on it" email.
Awaiting ReplyWaiting on the customerEmail 2 (needs info), remind at 3 days, auto-close at 5
ResolvedClosedStamp Date Resolved + Email 3 + optional CSAT survey
Step 2 · Custom fields

Two field sets: capture & record

The form writes to contact fields. The New sequence copies them onto opportunity fields, where the real ticket record lives - so a customer with multiple tickets never has one ticket overwrite another.

C Contact fields (the form fills these)

FieldTypeMerge key
Request CategoryDropdown{{contact.request_category}}
Support TypeDropdown{{contact.support_type}}
Describe Your IssueMulti-line text{{contact.describe_your_issue}}
Attachment URLText{{contact.attachment_url}}
Support Ticket Number (scratchpad)Text{{contact.support_ticket_number}}

O Opportunity fields (the ticket record)

FieldTypeMerge keyFilled by
Ticket NumberText{{opportunity.ticket_number}}New seq
Ticket CategoryText{{opportunity.ticket_category}}copy
Ticket Support TypeText{{opportunity.ticket_support_type}}copy
Ticket IssueMulti-line text{{opportunity.ticket_issue}}copy
Ticket Attachment URLText{{opportunity.ticket_attachment_url}}copy
Ticket Additional Info NeededMulti-line text{{opportunity.ticket_additional_info_needed}}Agent
Ticket ResolutionMulti-line text{{opportunity.ticket_resolution}}Agent
Ticket Date ResolvedDate{{opportunity.ticket_date_resolved}}Resolved seq (auto)
Merge keys
GHL auto-generates each merge key from the field name (lowercased; spaces and symbols become underscores - a "/" becomes a double underscore). The keys above assume these exact names; after creating the fields, open each one and confirm its real key before using it in emails and workflows.
Tip
Also create two Custom Values: company_name = your business name and support_sla = e.g. "24-48 hours", so the emails stay brand-driven and the SLA changes in one place.
Step 3 · The ticket number

A unique ID per ticket

A Recommended - date-time ID (zero dependency)

In the New sequence, set the ticket number to a formatted timestamp, e.g. TKT-{{right_now}} formatted to yyMMdd-HHmmss → TKT-260617-143052. Unique to the second; no counter needed. If two people submit in the same second, append the last digits of the contact id for an absolute guarantee. Uses GHL's date-field formatter - confirm the yyMMdd-HHmmss tokens render in a quick test before relying on them.

B Optional - sequential (TKT-1001, 1002…)

Keep a Custom Value counter and use a Math-Operation step (counter +1, write back, stamp the field). Only viable if this account's Math action can target a Custom Value - otherwise stay on the date ID. Either way the number is fully defined.

Step 4 · The support form

The one piece built by hand

A GHL form named Submit a Support Ticket. GHL's form create/clone API is unreliable, so the form is built in the form builder - but every custom field it maps to was created in Step 2, so it's just dragging those fields in.

1
New FormCreate it
  • Sites → Forms → Builder → + Add Form
  • Name it exactly Submit a Support Ticket - the New sequence's trigger matches this name
2
Standard FieldsWho's contacting you
  • First Name + Last Name + Email (required), Phone (optional)
3
Custom FieldsDrag in the existing fields
  • Search each by name and drag it in - dropdown options auto-load from the field
  • Request Category (required) · Support Type (required) · Describe Your Issue (required, multi-line)
  • Attachment URL (optional, for a Loom / Drive / screenshot link)
4
File UploadLet them attach a screenshot
  • Add a File Upload field (optional); allow images / PDF / short video. Stored on the contact, surfaced in the team notification.
5
On-SubmitReassure them
  • Show message: "Thanks - your ticket is in. Our team will respond within {{custom_values.support_sla}}, and a confirmation email is on its way." (or redirect to a thank-you page)
6
Style + EmbedBrand it and ship it
  • Match your brand button color, then Save → Integrate → embed on a /support page or wherever customers reach support

Request Category options (example)

  • Billing & payments
  • Technical issue / bug
  • Account & access
  • Onboarding & setup
  • Product question
  • Other

Swap these for your own products, departments, or service lines - the router reads whatever you put here.

Support Type options (example)

  • Booking or reschedule
  • Payment or billing
  • Login / access
  • Feature or how-to
  • General question
  • Other

A second axis so the team can triage by intent, independent of category.

Step 5 · Email templates

Four branded emails

Subjects and bodies use merge tags so each ticket personalizes itself. Set the From name/email to [Company] Support <support@yourcompany.com> and Reply-To to the same inbox, so customer replies thread back and trigger the reply handler.

New seq - on submitEmail 1 - Ticket Received · We've got it - Support Ticket #{{opportunity.ticket_number}}

Hi {{contact.first_name}},

Thanks for reaching out. We've received your request and opened Support Ticket #{{opportunity.ticket_number}}.

Our team is on it and will get back to you within {{custom_values.support_sla}}.

Need to add something? Just reply to this email and it'll attach straight to your ticket.

- The {{custom_values.company_name}} Support Team

Awaiting Reply seqEmail 2 - Additional Info Needed · One quick thing on Ticket #{{opportunity.ticket_number}}

Hi {{contact.first_name}},

We're working on Ticket #{{opportunity.ticket_number}} and need one more thing from you to move it forward:

What we need from you
{{opportunity.ticket_additional_info_needed}}

Just reply to this email with that and we'll pick it right back up.

Resolved seqEmail 3 - Resolved · Resolved - Support Ticket #{{opportunity.ticket_number}}

Hi {{contact.first_name}},

Good news - Ticket #{{opportunity.ticket_number}} has been resolved.

Resolution
{{opportunity.ticket_resolution}}

If anything still isn't right, just reply to this email and we'll reopen it. Thanks for your patience.

Awaiting Reply seq - after 3 daysEmail 4 - Reminder · Still need a quick reply - Ticket #{{opportunity.ticket_number}}

Hi {{contact.first_name}},

We're holding Ticket #{{opportunity.ticket_number}} open and still waiting on one thing from you:

What we need from you
{{opportunity.ticket_additional_info_needed}}

Reply whenever you have a moment and we'll finish up. If we don't hear back, we'll close the ticket in a couple of days - you can always reply to reopen it.

Reusable HTML shell (Gmail-safe - paste into GHL, swap the copy)

Acme.

Hi Jordan,

Thanks for reaching out. We've received your request and opened Support Ticket #TKT-260617-143052.

Our team is on it and will get back to you within 24-48 hours.

Need to add something? Just reply to this email and it'll attach straight to your ticket.

- The Acme Support Team

Acme • Support
Sample brand & copy shown - swap [Company] and the body for your own.
EMAIL-SHELL.HTML
<div style="background:#EDEDEA;padding:40px 16px;font-family:Helvetica,Arial,sans-serif;">
  <div style="max-width:620px;margin:0 auto;border-radius:6px;overflow:hidden;
              box-shadow:0 2px 24px rgba(0,0,0,.12);">
    <div style="height:4px;background:#FF7A1A;"></div>
    <div style="background:#0B1E3F;padding:30px;text-align:center;">
      <span style="color:#fff;font-size:24px;font-weight:800;">[Company]<span style="color:#FF7A1A;">.</span></span>
    </div>
    <div style="background:#fff;padding:48px 52px;font-size:16px;line-height:1.75;color:#243a5e;">
      <!-- EMAIL BODY COPY GOES HERE -->
    </div>
    <div style="background:#0B1E3F;padding:26px;text-align:center;font-size:11px;color:#9aa3b5;">
      {{custom_values.company_name}} • Support
    </div>
  </div>
</div>
Step 6 · Category routing

Tag the category, ping the team

The form captures Request Category, and the New sequence's router branch reads it: every category gets its own tag so the board stays sortable. The tag is what's category-specific; the owners (your support team) are constant. The customer emails stay shared across all categories.

CategoryTag addedRouting branch
Billing & paymentssupport-billingtag → notify support team
Technical issuesupport-technicaltag → notify support team (+ optional dev escalation)
Account & accesssupport-accesstag → notify support team
Onboarding & setupsupport-onboardingtag → notify support team
Product questionsupport-producttag → notify support team
Other (catch-all)support-generaltag → notify support team (the else so nothing is unrouted)
Why a router, not a pipeline per category
One Support pipeline keeps every ticket on one board the team works from. The category drives the tag (so the board stays sortable + a category-specific step can fire), while the stage drives the customer emails. If a single category later needs heavier automation (e.g. auto-grant access, or a dev-team handoff), split that branch out into its own standalone workflow triggered by its tag - the engine doesn't change.
Step 7 · One automation per stage

Six workflows, each editable on its own

Every stage is its own workflow, triggered by Opportunity Status Changed filtered to this pipeline + stage (the New sequence is form-triggered, since the form is what creates the ticket). Drag a card to a stage and that stage's sequence fires - nothing overlaps. One cross-stage reply handler keeps the stages honest. Build each as a Draft, then publish after a test ticket runs clean. Click a workflow to expand its steps.

flowchart TB F(["Form: Submit a Support Ticket"]):::form subgraph NEW["NEW - trigger: Form Submitted"] direction TB n1["1 - Set ticket number TKT-yyMMdd-HHmmss"]:::new --> n2["2 - Create Opportunity (always new) into New"]:::new n2 --> n3["3 - Copy contact fields into opportunity"]:::new n3 --> n4["4 - Send Email 1 (Ticket Received)"]:::mail n4 --> n5["5 - Category router: tag + notify team"]:::route end subgraph URG["URGENT - trigger: Stage to Urgent"] direction TB u1["1 - Tag priority-urgent"]:::urg --> u2["2 - Alert team (email + SMS)"]:::urg --> u3["3 - Task, due today"]:::urg end subgraph PROG["IN PROGRESS - trigger: Stage to In Progress"] direction TB p1["1 - Clear urgent tag"]:::prog --> p2["2 - Assign owner + work task"]:::prog --> p3["3 - Optional 'we're on it'"]:::prog end subgraph WAIT["AWAITING REPLY - trigger: Stage to Awaiting Reply"] direction TB w1["1 - Send Email 2 (info needed)"]:::mail --> w2["2 - Wait 3d, then Email 4 reminder"]:::wait w2 --> w3["3 - Wait 2d, then auto-close"]:::wait end subgraph DONE["RESOLVED - trigger: Stage to Resolved"] direction TB d1["1 - Stamp Date Resolved"]:::done --> d2["2 - Send Email 3 (Resolved)"]:::mail --> d3["3 - Optional CSAT"]:::done end subgraph REPLY["REPLY handler - trigger: Customer Replied"] direction TB r1{"Current stage?"}:::wf -->|Awaiting Reply| r2["Move to In Progress + notify"]:::prog r1 -->|Resolved| r3["Reopen to In Progress + notify"]:::prog r1 -->|else| r4["Notify: new reply"]:::prog end F --> NEW NEW -.->|drag: priority| URG NEW -.->|drag: start work| PROG URG -.-> PROG PROG -.->|fill Info Needed| WAIT PROG -.->|fill Resolution| DONE WAIT -.->|timeout| DONE REPLY -.->|reactivate / reopen| PROG classDef form fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F classDef wf fill:#0B1E3F,stroke:#0B1E3F,color:#FFFFFF classDef route fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F,stroke-dasharray:4 3 classDef mail fill:#FFF4EC,stroke:#FFA155,color:#0B1E3F classDef new fill:#EEF1F6,stroke:#5A6479,color:#0B1E3F classDef urg fill:#FBEAE8,stroke:#D2544A,color:#0B1E3F classDef prog fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F classDef wait fill:#FAF3E4,stroke:#C98A2E,color:#0B1E3F classDef done fill:#EAF4EE,stroke:#3FA66A,color:#0B1E3F
1
New sequenceNew
Trigger: Form Submitted - Form is "Submit a Support Ticket"
1
Update Contact FieldGenerate the ticket number
  • Field: Support Ticket Number
  • Value: type TKT- then insert {{right_now}} → set date format yyMMdd-HHmmss → e.g. TKT-260617-143052
  • Collision-proofing (optional): append -{{contact.id}}
2
Create OpportunityOpen the ticket as its own opportunity
  • Pipeline: Support Tickets  |  Stage: New  |  Status: Open
  • Name: {{contact.first_name}} {{contact.last_name}} - {{contact.support_ticket_number}}
  • Set "Allow Duplicate Opportunity" / create-new = ON so it never hijacks the contact's existing opportunity in another pipeline
3
Update Opportunity FieldCopy the form data onto the ticket record
  • Ticket Number = {{contact.support_ticket_number}}
  • Ticket Category = {{contact.request_category}}
  • Ticket Support Type = {{contact.support_type}}
  • Ticket Issue = {{contact.describe_your_issue}}
  • Ticket Attachment URL = {{contact.attachment_url}}
4
Send EmailConfirm receipt to the customer
  • Template: 01 - Ticket Received
  • From + Reply-To: support@yourcompany.com (so replies thread back and fire the reply handler)
5
If / Else - Category routerTag the category and ping the team
  • Condition on {{contact.request_category}} - one branch per category, each does Add Contact Tag + Internal Notification to the support team
  • Billing → support-billing · Technical → support-technical · Access → support-access …
  • Else → support-general so nothing is unrouted
  • Notification subject: New ticket {{opportunity.ticket_number}} - {{opportunity.ticket_category}} / {{opportunity.ticket_support_type}}

The only stage a ticket enters automatically. Build order matters: number → opportunity → copy fields → email → router. Everything below is manual-drag triggered.

2
Urgent sequenceUrgent
Trigger: Opportunity Status Changed - Pipeline "Support Tickets", Stage = Urgent
1
Add Contact TagFlag it
  • Tag: priority-urgent
2
Internal Notification - EmailAlert the team
  • Subject: URGENT - Ticket {{opportunity.ticket_number}} ({{opportunity.ticket_category}})
3
Internal Notification - SMSSame alert, second channel
  • Body: URGENT ticket {{opportunity.ticket_number}} needs eyes now.
4
Add TaskPut it on the board with a deadline
  • Title: URGENT: work ticket {{opportunity.ticket_number}}  |  Due: today

No customer email - Urgent is internal priority only. Agent SOP: drag any New / In Progress ticket here to escalate.

3
In Progress sequenceIn Progress
Trigger: Opportunity Status Changed - Pipeline "Support Tickets", Stage = In Progress
1
Remove Contact TagClear stale state
  • Remove priority-urgent (it's now actively worked, not just flagged)
2
Add TaskHand it to an owner
  • Title: Work ticket {{opportunity.ticket_number}} - {{opportunity.ticket_support_type}}  |  Due: +1 day
3
Send Email - optional"We're on it" touch
  • Off by default to avoid over-emailing. Turn on only if tickets tend to sit before pickup.

This is also where the Reply handler lands a reactivated/reopened ticket, so the owner is re-tasked automatically. GHL note: turn on Allow Re-Entry in this workflow's settings - a ticket can enter In Progress more than once (after a reopen or a reply), and with re-entry off the second entry won't fire.

4
Awaiting Reply sequenceAwaiting Reply
Trigger: Opportunity Status Changed - Pipeline "Support Tickets", Stage = Awaiting Reply
1
Send EmailAsk for the missing info
  • Template: 02 - Additional Info Needed - pulls {{opportunity.ticket_additional_info_needed}}
2
WaitHold up to 3 days, but let a reply break the wait
  • Wait type: condition with timeout
  • Condition: Stage is not Awaiting Reply (a reply moves it to In Progress → exits early)
  • Max wait: 3 days
3
If / ElseOnly remind if still silent
  • If still Awaiting Reply → Send Email 04 - Reminder; else do nothing
4
WaitGive 2 more days, same escape hatch
  • Condition Stage is not Awaiting Reply  |  Max wait: 2 days
5
If / Else - auto-closeNo response = close politely
  • If still Awaiting Reply → set Ticket Resolution = Closed - no response. Reply to reopen.
  • then move Stage to Resolved (hands off to the Resolved sequence)

GHL note: use the condition-with-timeout Wait, not a fixed Delay - a fixed delay can't be short-circuited by the customer's reply.

5
Resolved sequenceResolved
Trigger: Opportunity Status Changed - Pipeline "Support Tickets", Stage = Resolved
1
Update Opportunity FieldStamp the close date
  • Ticket Date Resolved = {{right_now}} (auto-stamp)
2
Send EmailTell the customer it's done
  • Template: 03 - Resolved - pulls {{opportunity.ticket_resolution}}
3
Wait + Send - optionalAsk how it went
  • Wait 1 day → one-question CSAT. Off by default; turn on once the core flow is proven.

This stage also receives auto-closed tickets from the Awaiting Reply timeout (resolution already filled with the "no response" text).

+
Reply handlercross-stage
Trigger: Customer Replied (inbound) - add it twice: Email + SMS
1
Trigger filterScope it to support tickets only
  • Condition: the contact has an opportunity in Support Tickets (most reliable) - or list the specific support- tags individually (GHL filters don't match tag wildcards) - so a normal sales reply doesn't trip this workflow
2
If / ElseBranch on the ticket's current stage
  • If Awaiting Reply → move to In Progress + notify: Customer replied with the info
  • Else-if Resolved → move to In Progress + notify: Ticket reopened
  • Else → notify only: New reply on the ticket

GHL note: if a contact has more than one open opportunity, confirm the action targets the Support opportunity (filter by pipeline) so a reply doesn't move the wrong card.

Upgrade · One open ticket at a time

One open ticket per customer

By default a customer can open several tickets and each stays isolated on its own opportunity. But in GHL the routing state - the priority tag, the awaiting-reply timer - lives on the contact, not the opportunity. So two of the same person's tickets moving through stages at once can cross-interfere. The clean fix: let a customer hold one open ticket at a time, and politely bounce a second submission until the first is resolved.

Why a gate
GHL opportunities can't carry tags, and a workflow enrolls the contact. So the priority-urgent tag and the Awaiting-Reply timer are contact-level - if one person has two live tickets, clearing one can clear the other. Enforcing one open ticket sidesteps that whole class of problem instead of fighting GHL's data model.
flowchart TD F["FORM: Submit a Support Ticket"]:::form --> G{"Has an open ticket?
contact tag: has-open-ticket"}:::wf G -->|Yes| B["Email 5 - Ticket Already Open
STOP - no new card"]:::mail G -->|No| A["Tag: ticket-approved"]:::route A --> NEW["NEW sequence:
clear ticket-approved, add has-open-ticket,
open the ticket, route to the team"]:::act NEW --> FLOW["ticket flows through the pipeline"]:::snew FLOW --> R["RESOLVED sequence also
removes has-open-ticket"]:::done R -.->|customer can now open a new one| F classDef form fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F classDef wf fill:#0B1E3F,stroke:#0B1E3F,color:#FFFFFF classDef route fill:#FFF1E6,stroke:#FF7A1A,color:#0B1E3F,stroke-dasharray:4 3 classDef mail fill:#FFF4EC,stroke:#FFA155,color:#0B1E3F classDef act fill:#FFFFFF,stroke:#FF7A1A,color:#0B1E3F classDef snew fill:#EEF1F6,stroke:#5A6479,color:#0B1E3F classDef done fill:#EAF4EE,stroke:#3FA66A,color:#0B1E3F
+
Intake gatefront door
Trigger: Form Submitted - Form is "Submit a Support Ticket"
1
If / ElseDoes this contact already have an open ticket?
  • Condition: contact has tag has-open-ticket
  • Use a tag condition, not a custom-field condition - GHL renders tag conditions natively (a custom-field condition shows a raw field id in the picker) and "has tag X" is the idiomatic check
2
Yes branch - Send EmailBounce the duplicate
  • Send 05 - Ticket Already Open (below), then the branch ends - no opportunity is created
3
No branch - Add Contact TagApprove it
  • Add tag ticket-approved - the hand-off that starts the New sequence

Why a separate workflow? It keeps the New sequence's steps untouched - the gate just decides whether to start it. A GHL If/Else branch can't "continue past" into shared steps, so wrapping the whole New sequence inside one branch would mean rebuilding it; a front-door gate avoids that.

T The open-ticket flag, start to finish

WhenWorkflowTag action
Form submitted, no open ticketIntake gateadd ticket-approved
Ticket createdNew sequence (first + after open)remove ticket-approved, add has-open-ticket
Ticket resolved (or auto-closed)Resolved sequenceremove has-open-ticket
Form submitted, ticket still openIntake gateno tag - sends the "already open" email and stops

One trigger change: with the gate on, the New sequence no longer triggers on the form directly - it triggers on Contact Tag added = ticket-approved (the gate owns the form). Its first step removes ticket-approved so the next ticket can re-add it.

Escape hatch
A flag only clears on Resolved, so a ticket parked forever in New would lock the customer out. The 5-day Awaiting-Reply auto-close covers anything that reaches Awaiting; for a ticket marked Lost or left in New, the agent (or a small "on Lost → remove has-open-ticket" workflow) clears it. Keep the tag removable so support can always unlock a customer by hand.
Intake gate - duplicateEmail 5 - Ticket Already Open · You already have an open ticket - #{{contact.support_ticket_number}}

Hi {{contact.first_name}},

Thanks for reaching out. You already have an open support ticket with us - #{{contact.support_ticket_number}} - and our team is on it.

To keep everything in one place, we'll reply to that existing ticket. There's no need to submit another - just reply to its confirmation email if you have more to add.

- The {{custom_values.company_name}} Support Team

Important setting

Cross-pipeline handling

A customer is often already an opportunity in another pipeline - a lead, a buyer, a subscriber. GHL models a person on two pipelines as one contact with multiple opportunities, so:

Edge cases

All covered

CaseHandled by
Same customer opens 2+ ticketsTicket data on the opportunity = each ticket independent (no overwrite)
Customer submits again while a ticket is openIntake gate bounces it with the "already open" email - one open ticket per customer (optional upgrade)
Ticket is about a specific categoryThe New sequence's router tags the category + notifies the team
Customer replies during Awaiting ReplyReply handler moves it back to In Progress + notifies
Customer replies after ResolvedReply handler reopens to In Progress + notifies
Customer never replies (stuck)Awaiting Reply reminds at 3 days, auto-closes at 5
No extra info needed (straight to fix)Agent fills Resolution, drags to Resolved; Awaiting Reply is optional
File / screenshot attachedForm file field stored on contact; link surfaced in the team notification
Ticket # uniquenessDate-time-to-the-second (optionally + contact-id tail); sequential counter is the optional upgrade
Make it yours

Customize & extend

Quick swaps

  • Category list + tags in the router to match your products / departments
  • SLA in support_sla (same-day, 24-48 hrs)
  • Support Type options as your offer mix changes
  • Voice of the four emails to match your brand

Optional add-ons

  • A CSAT one-question survey after Resolved
  • A self-serve deflection (link a help center before a ticket opens)
  • SLA-breach alert if New / In Progress sits unworked past X hours
  • A /support page embedding the form
Ship it

Build checklist

The mindset

The stage is the status. The opportunity is the ticket. Nobody writes a reply by hand.

Swap the brand, the categories, and the team - the engine stays exactly the same.