Owner: Alliyson Bonner (Head of Field Ops & Claims) — operational owner
Quality oversight: Purple (VP of Product Quality and Experience)
Engineering escalation: Robert "Rep" Taylor (Head of QA Engineering & AI Automation)
Author: Workflow Architect (Amena) — for Chief of Staff team
Status: Draft v0.2 — pending sign-off from Alliyson, Purple, Heaven, Alicia, Jonathan
Effective: TBD — after sign-off + Chatwoot routing rules go live
Pairs with: docs/ops/op3-chatwoot-routing-rules.md (routing implementation), docs/observability/op3-sla-dashboard.md (SLA telemetry)
Supersedes: None (first formal CS escalation SOP)
Related SOP: §17.4b — Vendor-portal support escalation (separate doc, not yet authored)
This SOP governs B2C customer-support tickets entering HempDash through any of these channels:
docs/CHATWOOT_AGENT_BOT.md)support@gethempdash.comPrimary: Heaven Lee (@heaven) — T1 floor lead, full-day coverage including evenings & weekends. Dallas TX; willing to travel throughout Texas for field-followup scenarios.
Mentor / experienced backstop: Alicia Garrett (@alicia.garrett21) — school-year working window ~09:00–14:45 CT M–F (hard constraint, see §2.1.1). Non-school-year hours more flexible, TBD with her. Dallas TX.
Out-of-hours backup: Captain AI agent bot replies + drops a @lola private note (current behavior per docs/CHATWOOT_AGENT_BOT.md)
Knowledge-transfer cadence: Weekly 30-min mentor check-in (Alicia → Heaven) every Friday 11:00 CT, plus async pairing on any ticket Heaven flags. Purple observes the first 4 sessions for quality calibration.
Alicia has in-person family commitments at 08:00 and 15:00 CT M–F during the school year (Texas school year — see §2.1.2 calendar). Her required working window is 09:00–14:45 CT M–F school-year. After 14:30 CT M–F (school year), she is OFF the auto-assignment queue — Heaven absorbs T1. Alicia may pick up async tickets after 14:45 CT if she has time but it is NOT required.
Routing implication: No T1 ticket auto-assigned to Alicia after 14:30 CT M–F during the school year. After 14:30 CT M–F (school year), Heaven only. Weekends → Heaven only. Non-school-year → Alicia's hours flexible, default to round-robin with Heaven during business hours, confirm with her per term.
SLA accountability: SLA breaches attribute to whoever was on the queue at the breach moment, not to the named tier owner. If a ticket Alicia opened at 14:00 breaches at 16:00, the breach attributes to Heaven (on-queue at breach time), not Alicia.
The routing logic needs to know when Texas school year is in session. v0.1 default: (A) hardcoded TX school-year dates in a small const (mid-August through early-June, minus standard observed school holidays — Thanksgiving week, winter break ~Dec 20–Jan 5, spring break mid-March, Memorial Day, observed federal holidays during the school year). TODO (post-launch): upgrade to (B) Google Calendar feed Alicia maintains, OR (C) a Chatwoot custom attribute toggle. v0.1 lives in lib/utils/school-year-calendar.ts (to be created — Plane ticket linked from §11 Q6).
T1 staffing-schedule table:
| Window (CT) | Heaven | Alicia (school year) | Alicia (non-school-year) |
|---|---|---|---|
| 00:00–09:00 M–F | On queue (overnight + early) | Off | Off |
| 09:00–14:30 M–F | On queue (round-robin w/ Alicia) | On queue (round-robin w/ Heaven) | Flexible, TBD per term |
| 14:30–14:45 M–F | On queue | Off auto-assign (mentor handoff window) | Flexible, TBD per term |
| 14:45–24:00 M–F | On queue (sole T1) | Off (async-pickup optional) | Flexible, TBD per term |
| All-day Sat/Sun | On queue (sole T1) | Off | Off |
Scope — T1 team (Heaven + Alicia) owns these ticket types:
SLAs:
Definition of "first response": A human (or Captain AI) message acknowledging the customer with at least one substantive sentence — not an auto-ack. Auto-ack does NOT stop the SLA clock.
Definition of "resolution": Customer has the answer they need AND the ticket is marked Resolved in Chatwoot. Reopens reset the clock.
Owner: Alliyson Bonner (@alliyson — Head of Field Ops & Claims). Starts week of May 5 (per user_alliyson.md). 5yr State Farm subrogation/arbitration background — claims, disputes, refunds, chargebacks, suspension appeals are her direct wheelhouse. Willing to travel for on-site followup and inspector walk-through prep (also covers SOP 17.3 step 4 on-site recall disposition).
Escalation-review backup: Jonathan Sullivan (Founder)
Operational oversight: Lola (Laura Daniels — Chief of Staff / Head of Operations) — handles T2 scheduling, queue oversight, and Tier-3-cadence coordination per OP6. Lola does NOT take T2 tickets directly post-transition.
While Alliyson onboards, Lola covers T2 incoming as interim owner. Alliyson shadows + does mentored handoffs:
| Week | Alliyson | Lola |
|---|---|---|
| Week 1 (May 5–11) | Shadow every T2 ticket, observe-only | Owns T2 queue, narrates decisions for Alliyson |
| Week 2 (May 12–18) | Co-handles tickets, drafts replies Lola reviews | Reviews + approves Alliyson's drafts, hands off completed tickets |
| Week 3 (May 19+) | Full ownership of T2 queue | Steps back to oversight + scheduling only |
Cutoff for full handoff: May 19. If Alliyson is not ready by May 19, Lola + Jonathan jointly extend by 1 week and document the reason. See §11 Q8.
Scope — Tier 2 owns these ticket types:
SLAs:
Owner: Robert "Rep" Taylor (Head of QA Engineering & AI Automation)
Backup: On-call rotation (currently Rep + Amena agent coverage)
Scope — Tier 3 owns these ticket types:
lawsuit, regulator, TABC, DSHS, medical emergency, hospital, injury, BBB, per lib/services/chatwoot-agent-bot/handoff.tsSLAs:
Note: T3 SLAs are deliberately longer because engineering work has hard floor on diagnosis + fix + deploy time. A T3 ticket the customer needs answered FAST should be escalated as a P0 incident (see §10 SLA breach handling), not held in the T3 queue.
A ticket does NOT move up a tier until the current owner runs the gate for their tier. Skipping the gate is the most common preventable cause of bad escalations.
Before tagging a ticket for T2, the T1 agent on the ticket must complete all four:
#incidents channel in Mattermost for the last 24h. If the ticket matches a known incident, reply with the standard incident-acknowledgment template (see §5.3) and link it to the incident thread instead of escalating.Before tagging a ticket for T3, the T2 owner must complete all three:
customer_id). Confirm the failure is reproducible in logs, not a one-off transient.If T2 receives a T1-escalated ticket where the gate was not run, T2 sends it back to T1 with the missing gate item tagged. Audit row written. Three gate-skips in a 30-day window triggers a 1:1 coaching session (Purple owns the coaching conversation, since quality is her lane).
Same flow applies T3 → T2.
Every tier-up escalation requires the escalating agent to fill a fixed-format handoff template as the first private note on the conversation. Free-text "hey can you look at this" is not allowed.
ESCALATION — T1 to T2
=====================
Customer:
- Name: <full name>
- Email: <email>
- Customer ID: <UUID>
- VIP flag: <yes/no — pulled from customer profile>
- Open ticket count: <N>
- Prior escalation in last 7d: <yes/no — link if yes>
Ticket summary (2-3 sentences):
<what the customer is asking for, in plain language>
What T1 tried (gate evidence):
- KB search: <article URL or "no match">
- Auth0 state: <verified clean / flagged: <issue>>
- Known issue check: <not in #incidents / matches: <incident link>>
- Screenshot: <attached / N/A — non-visual>
Where stuck:
<one sentence: what's blocking T1 from resolving>
Customer emotional state:
<calm / frustrated / angry / panicked — your read>
<if not calm: any quotes that matter for tone-matching the next reply>
Recommended T2 action:
<one sentence: what T1 thinks T2 should do>
Reason tag (pick one):
[ ] refund_dispute_over_50
[ ] suspension_appeal
[ ] feature_behavior_question
[ ] content_moderation
[ ] repeat_complaint_7d
[ ] chargeback_notification
[ ] multi_customer_pattern
[ ] other: <free text>
ESCALATION — T2 to T3
=====================
[ALL FIELDS FROM T1→T2 HANDOFF — preserved]
T2 investigation:
- Backend logs: <Grafana log URL>
- Sentry event: <event URL or "no Sentry match">
- Prior similar issues: <Plane/Chatwoot links or "none">
- Reproduction steps T2 verified: <numbered steps>
Hypothesis:
<T2's best guess at root cause, even if uncertain>
Customer-facing impact:
<one sentence: blast radius — single customer / cohort / all customers>
<severity guess: P0 / P1 / P2 / P3 per cert metrics taxonomy>
Recommended T3 action:
<one sentence: what T2 thinks engineering should do>
Reason tag (pick one):
[ ] http_5xx_error
[ ] http_4xx_pattern
[ ] data_loss
[ ] data_corruption
[ ] security_concern
[ ] reproducible_bug
[ ] t2_could_not_resolve
[ ] auto_t3_keyword (from Captain bot — see §8.3)
[ ] other: <free text>
Tone-matched to HempDash voice: warm, professional, plain, no corporate stiffness. Per feedback_amena_voice.md: standard spelling, natural rhythm, no folk-spelling. Read each one aloud — if the rhythm is natural and a copy editor would let it pass, it lands right.
All templates use second-person, present-tense, active voice. Customer name where bracketed. Sign with the agent's first name.
Hi {first_name}, thanks for reaching out. I'm looking at this now and will get back to you with details shortly. If you've got a screenshot or order number that'd help me move faster, send it whenever you can.
—
Hi {first_name}, want to make sure I solve this right the first time. Can you send me {specific_item — screenshot / order number / email used at signup / timestamp}? Once I've got that I can take care of it.
—
Hi {first_name}, you're seeing something our team is actively working on. We've got eyes on it and I'll follow up here as soon as it's resolved — should be within {timeframe}. Sorry for the trouble.
—
Hi {first_name}, this one needs a closer look from our product team. I've passed all the details over and someone will reach back out by {SLA_due_at — 1 business day}. You're in good hands.
—
Hi {first_name}, our engineering team is taking a look at this. They've got everything they need from my side. You'll hear back from us by {SLA_due_at — 3 business days at the outside}. Thanks for your patience while we get this right.
—
Hi {first_name}, all squared away. Here's what I did: {one or two sentences, plain language, what changed}. If anything still looks off, reply here and I'll pick it back up. Thanks for working through this with us.
—
Hi {first_name}, thanks for letting us know. I've flagged this for our product lead personally — you'll hear back from them today, not from me. Anything time-sensitive in the meantime, this thread is the fastest way to reach me.
—
Every tier-up and tier-down handoff writes both a Chatwoot conversation tag and a row in the support_escalations table.
Chatwoot tags applied at escalation time:
tier:t1 | tier:t2 | tier:t3 (current tier — exactly one applied at any time)escalated_from:t1 | escalated_from:t2 (where it came from)reason:<reason_tag> (matches the picklist in §4.1 / §4.2)vip:true if VIP customersla_breached:true if any SLA was breached on this ticketsupport_escalations table schemaIf the table does not exist, treat this as a backend follow-up (Plane ticket, owner: Rep). Spec for the table:
CREATE TABLE support_escalations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
chatwoot_conv_id INT NOT NULL, -- Chatwoot conversation ID
ticket_id TEXT NOT NULL, -- Chatwoot display ID (e.g. "#1234")
customer_id UUID NOT NULL, -- HempDash internal customer ID
from_tier TEXT NOT NULL, -- 't1' | 't2' | 't3'
to_tier TEXT NOT NULL, -- 't1' | 't2' | 't3'
at_time TIMESTAMPTZ NOT NULL DEFAULT now(),
by_agent_id TEXT NOT NULL, -- Chatwoot agent ID who escalated
by_agent_name TEXT NOT NULL, -- denormalized for dashboard speed
reason_tag TEXT NOT NULL, -- matches picklist in §4
is_gate_skip BOOLEAN NOT NULL DEFAULT false, -- true if gate evidence missing
notes TEXT, -- optional free text
CONSTRAINT support_escalations_tier_check
CHECK (from_tier IN ('t1','t2','t3') AND to_tier IN ('t1','t2','t3'))
);
CREATE INDEX idx_support_escalations_customer ON support_escalations(customer_id);
CREATE INDEX idx_support_escalations_conv ON support_escalations(chatwoot_conv_id);
CREATE INDEX idx_support_escalations_at_time ON support_escalations(at_time DESC);
Migration ownership: Per project_katherine_migration_gate.md, all Alembic/Prisma migrations from the Rep/Purple work-stream go through Katherine for review first. This migration follows that gate.
The Chatwoot routing-rule automation (per docs/ops/op3-chatwoot-routing-rules.md) writes the audit row whenever a tier-tag changes on a conversation. Manual escalations done by humans through the Chatwoot UI also trigger the write because the tier tag is the source-of-truth signal.
The Grafana SLA dashboard (per docs/observability/op3-sla-dashboard.md) reads this table for the escalation-rate heatmap and the per-tier conversion percentages. Postgres direct read is acceptable; do not pipe to Loki.
The SLA clock starts at whichever happens first:
The clock pauses when:
pending_customer_reply (T1 asked for screenshot, waiting on customer)Clock resumes when:
The clock resets when:
@lola private note; SLA clock starts at 08:00 CT the next business dayDefinition of VIP: Customer profile flag vip:true (set manually by Lola or Jonathan, OR set automatically by lifetime spend > $X — threshold TBD).
Behavior:
Definition: Any incoming message that mentions a regulator by name (DSHS, TABC, FDA, USDA, FTC, state AG offices, BBB), OR forwards an inquiry from a regulator's official address, OR includes phrases like "regulatory complaint", "agency inquiry", "official investigation".
Behavior:
tier:t3, reason:regulator_complaint, compliance_flag:true@balboanyc) in Mattermost with the conversation URL@balboanyc reviews any outbound message before it goes to the customer/regulatorDefinition: Anyone identifying as press, journalist, podcast host, reporter, blogger, news outlet, or any media-credential signature.
Behavior:
tier:out_of_scope, reason:media_inquiryDefinition: Threats of physical violence, sustained verbal abuse, slurs, or anything any T1 agent (Heaven or Alicia) flags as crossing the line.
Behavior:
tier:t2, reason:abuse_or_threatfeedback_lola_urgent_marking.md) regardless of assignee, since AR action is Lola's call.Definition: Any indication the account holder or person referenced is under 21 — keyword match on minor, under 21, under 18, my kid, my son, my daughter, my child, age numerics under 21 in a self-disclosed context.
Behavior:
tier:t3, reason:minor_user_concern, compliance_flag:true@rep + @lola + @balboanyc in MattermostDefinition: Per lib/services/chatwoot-agent-bot/handoff.ts keyword set: lawsuit, lawyer, attorney, sue.
Behavior:
tier:t3, reason:litigation_language@lola + @alliyson + Jonathan in MattermostSometimes a ticket sent up gets sent back down. That's not a failure — it's the system working.
Allowed tier-down paths:
Tier-down also writes an audit row with from_tier > to_tier. Same Chatwoot tag update applies (new tier: tag, plus de_escalated:true tag).
Tone with the customer on tier-down:
Do NOT tell the customer "this is going back to T1." From their view, the conversation is continuous. Internal routing is internal.
1 hour before SLA breach, the routing-rule automation (per docs/ops/op3-chatwoot-routing-rules.md) sends a Mattermost DM to the current tier owner:
"Ticket #{ticket_id} ({customer_first_name}) is 1 hour from SLA breach. Current tier: T{N}. Reply or escalate."
At SLA breach:
sla_breached:true is appliedis_gate_skip:true if T1 promotes without complete gate evidenceIf a single tier breaches 5+ SLAs in a 1-hour rolling window, the SLA dashboard (per docs/observability/op3-sla-dashboard.md) fires the team-wide PagerDuty page. This is "the queue is on fire, get help." Lola owns the response.
If any T1 agent's individual queue is >15 open tickets for >2 hours, the dashboard sends Lola a workload nudge (NOT a page). This is a workload-distribution signal, not a performance flag. Lola decides whether to redistribute across the T1 team (Heaven ↔ Alicia, subject to §2.1.1 hours) or bring backup online.
This wellness nudge is operational, not personal — it does NOT reference any team member's health, rest, or wellness protocols. Per feedback_purple_directive.md and user_purple.md, wellness-protocol context never appears in team-visible artifacts. Treat queue-depth nudges as workload signals only.
| # | Question | Who owns the answer | Why it matters |
|---|---|---|---|
| Q1 | VIP threshold — lifetime spend > $X qualifies. What is X? | Lola + Jonathan | Determines auto-T2 routing. Default: manual flag only until threshold defined. |
| Q2 | support_escalations table — does it exist in the current backend schema, or is this a new migration? |
Rep (backend confirmation) | If new, Plane ticket needed; Katherine reviews per migration gate. |
| Q3 | Captain AI auto-ack — current bot drops @lola notes on handoff (per docs/CHATWOOT_AGENT_BOT.md). Should the auto-ack message be substantive (start the SLA clock) or a placeholder (clock starts at human reply)? |
Heaven + Alicia + Lola | Changes the SLA clock-start contract in §7.1. |
| Q4 | "Repeat customer complaints" trigger says 2+ tickets in 7 days. Same customer, any topic? Or same customer + same topic? | Lola + Alliyson + T1 team | Materially changes T2 inbound volume. |
| Q5 | Holiday calendar — where is it canonically maintained (Google Calendar? Plane? Chatwoot custom?) so the SLA business-hours calculator can read it? | Lola | Implementation detail; blocking for §7.4 calculator. |
| Q6 | School-year calendar (per §2.1.2) — v0.1 default is hardcoded TX dates. When do we upgrade to a maintained feed, and which approach (B Google Calendar / C Chatwoot custom attribute)? | Alicia + Lola | Routing accuracy across summer + holidays. |
| Q7 | Chatwoot CSAT plugin enabled? (Drives dashboard Panel 9 + post-resolution sentiment metric.) | Lola | Implementation detail; blocking for dashboard Panel 9. |
| Q8 | Alliyson transition-window cutoff — default proposed: 2 weeks of shadowing (May 5–18), full handoff May 19. Confirm or extend based on Alliyson's actual readiness at week-2 review. | Lola + Jonathan | Sets the date Lola steps back to oversight-only. |
| Q9 | Pronouns for Alicia — user_alicia.md says unknown / default neutral. Confirm at onboarding so customer-facing comms use her name + correct pronouns. |
Alicia herself | Comms hygiene. |
Resolved questions (kept here as cross-reference):
This SOP is considered LIVE when:
docs/ops/op3-chatwoot-routing-rules.md is implemented and the routing automation passes a 5-ticket smoke testsupport_escalations table exists — migration shipped, Katherine reviewed, write path confirmed working on staginglib/utils/school-year-calendar.ts exists with TX school-year dates; routing rules read from it to enforce §2.1.1 hard constraintdocs/observability/op3-sla-dashboard.md is implemented in Grafana under "Operations" folder| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-05-11 | Workflow Architect (Amena) | Initial draft. Pending sign-offs + §11 resolution. |
| 0.2 | 2026-05-11 | Workflow Architect (Amena) | T1 ownership corrected to Heaven (primary) + Alicia (mentor); added §2.1.1 Alicia's hard schedule constraint and §2.1.2 school-year calendar; T2 ownership confirmed Alliyson Bonner with §2.2.1 2-week transition window (Lola interim); resolved v0.1 Q1; added new Q6 (school-year-calendar feed upgrade), Q7 (CSAT plugin status), Q8 (Alliyson transition-window cutoff). |
SOP index · VIP Dispute · Communication Standards · Channel Routing Map · Home