Data, opens a Zendesk ticket when the articles don’t help, tells the customer on their own channel when Zendesk marks the ticket solved, and reminds customers with open tickets every morning. It is six files, and every TypeScript file compiles against lua-cli 3.33.0; run it with the steps on Running any example.
Verified against lua-cli 3.33.0.
The conversation
- The customer asks a question. The model calls
search_knowledge_base, which runs a semantic search over thehelp_articlescollection and returns the closest articles with a score. - If nothing relevant comes back, the model offers a ticket and calls
create_ticket. The tool takes the requester’s email from the profile the channel verified, creates the ticket through Zendesk’s REST API, and stores a copy insupport_tickets. - When a support agent solves the ticket, Zendesk calls the
zendesk-ticket-eventswebhook, which finds the customer by email and sends a message on the channel they last used. - Every day at 10:00, the
ticket-follow-upjob messages every customer whose stored ticket is still open.
Primitives and channels
- Skill and tools:
support, withsearch_knowledge_baseandcreate_ticket. - Webhook:
zendesk-ticket-events, authenticated with the bearer token you set on the Zendesk webhook. - Job:
ticket-follow-up, cron0 10 * * *inEurope/London, two attempts. - Runtime objects:
Data.search,Data.create,Data.get,User.get,user.send, andenvforZENDESK_SUBDOMAIN,ZENDESK_EMAIL,ZENDESK_API_TOKEN, andZENDESK_WEBHOOK_TOKEN. - Channels: any inbound channel.
user.senddelivers where the customer last wrote when that is WhatsApp, Messenger, Instagram, Teams, SMS, or MessageBird, and resolvestruewhether or not it delivered, so confirm withlua logs; for WhatsApp, Send proactive messages explains the 24-hour window.
The code
The search tool returns the top five articles above a similarity of 0.7.src/skills/tools/SearchKnowledgeBaseTool.ts
src/skills/tools/CreateTicketTool.ts
context tells the model to search before it offers a ticket.
src/skills/support.skill.ts
ticket.solved.
src/webhooks/ZendeskWebhook.ts
src/jobs/TicketFollowUpJob.ts
src/index.ts
First run
Set the Zendesk values for local runs, then run the search tool. With an emptyhelp_articles collection it returns no articles, which is the branch that leads to a ticket.
Output
User.get returns null for an email no end user has, so neither sends anything.
lua chat --ci -e sandbox -m "How do I reset my password?" -t; it uploads the .env values with the sandbox version, so the same four variables apply. Then release it with lua push all --ci --force, lua version create --ci -m "<message>", and lua version promote <n>; Release an agent to production explains what each command changes.
Ways to make it yours
- Fill
help_articleswithData.create('help_articles', article, searchText), wheresearchTextis the title and body joined; Store and search data shows the pattern. To skip the search tool entirely, upload documents in the admin dashboard and use the knowledge feature. - In Zendesk, create a webhook that posts the
bodySchemaJSON from its payload template, with bearer-token authentication set to the value ofZENDESK_WEBHOOK_TOKEN; Handle a webhook explains where the URL comes from and when to use a Luasecretinstead. - Swap Zendesk for another ticketing system by changing
create_ticketand the webhook’sbodySchema; the skill, job, and persona stay the same. - Escalation rules such as the $500 threshold live in the persona, so an operator can change them without a release.
Next steps
Handle a webhook
Secret, signature check, and idempotent handling for events from your systems.
Schedule a job
Cron, interval, and one-time schedules, retries, and how to run a job by hand.
Store and search data
Collections, filters, and semantic search with
Data.
