Objective
A support inbox never arrives sorted. A furious billing dispute from an enterprise account sits in the same queue as a thank-you note and a routine "where's my order?" — and a human has to read every one to decide how urgent it is and who should own it. That triage is the slowest, most error-prone step in support, and it happens before anyone has even started helping.
This recipe does the triage inside the database. Every message is scored
with SENTIMENT() — a SQL function that returns positive, negative, or
neutral using the engine's built-in model, no Hugging Face endpoint and no
Python service. Then a plain-SQL routing rule turns sentiment + customer tier +
a couple of keywords into a concrete assignment: team, owner, and priority.
The final step hands the hardest message to an in-database agent (AGENT_RUN)
that writes the routing rationale and a suggested first reply. One table, one
connection, one auth token.
Step 1: Create the inbox and the routing directory
recipe_support_inbox holds raw messages exactly as they land from email/chat.
recipe_support_teams is a tiny directory of who handles what, so routing
decisions point at a real owner.
CREATE TABLE IF NOT EXISTS recipe_support_inbox (
message_id INTEGER PRIMARY KEY,
received_at TIMESTAMP,
channel TEXT, -- email | chat | twitter
customer_name TEXT,
customer_tier TEXT, -- free | pro | enterprise
subject TEXT,
body TEXT
);
CREATE TABLE IF NOT EXISTS recipe_support_teams (
team TEXT PRIMARY KEY,
owner TEXT,
handles TEXT
);
Step 2: Seed a realistic inbox
Ten messages that look like a real Monday morning: praise, a churn-risk enterprise complaint, a double-charge, an outage, and routine questions.
INSERT INTO recipe_support_inbox (message_id, received_at, channel, customer_name, customer_tier, subject, body) VALUES
(1, '2026-08-03 08:02:00', 'email', 'Dana Whitfield', 'enterprise', 'Incredible onboarding', 'Your onboarding team was amazing this week - we were live in two days. Thank you!'),
(2, '2026-08-03 08:07:00', 'email', 'Marcus Reyes', 'enterprise', 'Charged twice, no refund', 'I have been charged twice this month and three emails to billing have gone unanswered. If this is not fixed today we are cancelling our contract.'),
(3, '2026-08-03 08:11:00', 'chat', 'Priya Nair', 'pro', 'Order status?', 'Hi, can you tell me the status of order #4471? It has been a few days.'),
(4, '2026-08-03 08:15:00', 'email', 'Tom Bexley', 'pro', 'URGENT: production is down', 'Our production integration is down and your API has been returning 500 errors for the last hour. This is business critical.'),
(5, '2026-08-03 08:19:00', 'twitter','Sofia Almeida', 'free', 'love it', 'just wanted to say the new dashboard is gorgeous, great work team!'),
(6, '2026-08-03 08:24:00', 'email', 'Henry Osei', 'enterprise', 'Considering alternatives', 'We are frustrated with the slow response times and are evaluating competitors. We need a call this week.'),
(7, '2026-08-03 08:30:00', 'chat', 'Aiko Tanaka', 'free', 'How do I reset my password?', 'Where is the password reset link? I cannot find it in settings.'),
(8, '2026-08-03 08:36:00', 'email', 'Gabriel Santos', 'pro', 'Refund not received', 'The refund you promised last week still has not arrived and I am starting to lose patience.'),
(9, '2026-08-03 08:41:00', 'email', 'Lena Vogt', 'enterprise', 'Renewal + expansion', 'We are thrilled with the platform and want to talk about expanding to two more teams next quarter.'),
(10,'2026-08-03 08:47:00', 'chat', 'Omar Haddad', 'free', 'Feature question', 'Does the export support CSV as well as JSON? Just checking before I upgrade.');
INSERT INTO recipe_support_teams (team, owner, handles) VALUES
('Escalations', 'Sarah Chen (Sr. CSM)', 'Angry or churn-risk enterprise accounts'),
('Billing', 'Ravi Kapoor', 'Charges, refunds, invoices'),
('On-call SRE', 'Ops pager rotation', 'Outages, 5xx errors, downtime'),
('Tier-1 Support', 'Support queue', 'General questions, how-tos'),
('Advocacy', 'Mia Torres (Community)', 'Happy customers, testimonials, expansion');
Step 3: Ground truth — read the inbox by eye
Before any AI, look at what you would route manually. This is the check you compare the machine against.
SELECT message_id, customer_tier, channel, subject
FROM recipe_support_inbox
ORDER BY message_id;
Expected: 10 rows. By eye, the obvious escalations are #2 (double charge + cancel threat, enterprise), #4 (production outage), and #6 (churn-risk enterprise). #1, #5, #9 are clearly happy. The rest are routine.
Step 4: Classify sentiment with one SQL function
SENTIMENT(text) returns a single word — positive, negative, or neutral.
No model to download, no endpoint to call.
SELECT message_id, customer_name, subject, SENTIMENT(body) AS sentiment
FROM recipe_support_inbox
ORDER BY message_id;
Expected: praise (#1, #5, #9) → positive; the billing disputes, the churn-risk note, and the outage (#2, #4, #6, #8) → negative; routine questions (#3, #10) → neutral. Borderline messages tip on tone — the terse password-reset ask (#7) comes back negative because the model reads its mild frustration. These labels are the model's judgment, not a lookup table, so treat them as signal, not gospel.
Persist the label so downstream routing and reporting don't re-run the model:
ALTER TABLE recipe_support_inbox ADD COLUMN sentiment TEXT;
UPDATE recipe_support_inbox SET sentiment = SENTIMENT(body);