Every booth team knows the pattern: three conversations happening at once, a badge scanner that half works, and a stack of business cards that becomes a spreadsheet — eventually. By the time sales follows up, the attendee has been home for a week and the context is gone.
event-sponsorship-agent is a code sample that replaces that workflow with an AI agent. Attendees text a number, message over WhatsApp, or open an in-browser chat on a branded microsite. The agent runs a giveaway, answers product questions in the attendee's language, qualifies leads through a short conversation, and — the part that matters — pages the sales team by SMS the moment a hot lead asks for a demo. Everything runs in a single Telnyx Edge function.
Repo: https://github.com/team-telnyx/telnyx-code-examples/tree/main/event-sponsorship-agent
What is event-sponsorship-agent? A TypeScript sample that runs a sponsorship activation — giveaway entry, multilingual product Q&A, demo booking, lead capture, and real-time hot-lead routing — inside one Telnyx Edge function on a custom domain. Agent actors own conversation state, KV enforces rate limits, and SQLDB stores leads.
What the App Does
- Branded microsite on a custom domain (e.g.,
telnyx-at-reinvent.com) with an in-browser chat client — no separate hosting, no separate frontend deploy. - Three conversational channels, one brain. SMS, WhatsApp, and web chat all funnel into the same agent logic.
- A voice integration path. The signed voice webhook records the caller and queues the handoff; the current sample leaves the full Call Control conversation as the next implementation step.
- Giveaway entry as the hook. Attendees text or type a keyword to enter; the agent naturally transitions into a qualification conversation: name, company, use case, team size, timeline.
- Multilingual by default. Language is detected per message via Telnyx Inference, and the agent responds in kind — no "select your language" menu.
- Product Q&A. Attendees ask questions mid-flow and get contextual answers without derailing the qualification.
- Lead capture in SQLDB. Every answer upserts a lead record keyed on phone number, so returning attendees update rather than duplicate.
- Real-time hot-lead routing. When a lead is qualified and requests a demo, the agent SMSes the sales team with full context — while the attendee is still standing at the booth.
- Scheduled follow-ups. After the event, the agent follows up on each attendee's preferred channel: SMS, WhatsApp, email, or voice.
- Attribution reporting. A
/api/reportendpoint returns captured, qualified, and converted leads broken down by channel and use case. - Per-attendee rate limiting to protect the number, the inference budget, and the giveaway from abuse.
How It Works
One Edge function, multiple entry points
The entire activation lives in src/index.ts. Inbound webhooks and API routes all resolve to the same place:
// src/index.ts — one Edge function fronts every channel
export default {
async fetch(request: Request, env: SponsorEnv): Promise<Response> {
const { pathname } = new URL(request.url);
switch (pathname) {
case "/webhook/sms": {
const body = await request.json();
const from = body.data.payload.from.phone_number;
// One actor per attendee, keyed by phone number
const id = env.SPONSOR_AGENT.idFromName(from);
return env.SPONSOR_AGENT.get(id).fetch(request);
}
case "/webhook/voice":
return handleVoiceWebhook(request, env); // signed voice integration scaffold
case "/api/chat":
return handleChat(request, env); // in-browser chat
case "/api/report":
return attributionReport(env); // SQLDB attribution
default:
return serveMicrosite(request, env); // branded microsite
}
},
};
There is no separate server, no queue, no cron. The Edge function is the activation.
A stateful agent per attendee
The interesting design decision is the actor pattern. Each attendee gets their own SponsorAgent — a single-threaded, stateful actor instantiated via an ActorNamespace. If someone texts twice in quick succession, the messages are processed in order against the same session instead of racing two stateless handlers.
export class SponsorAgent extends Agent<SponsorEnv, SessionState> {
async processMessage(input: InboundMessage) {
// 1. Rate limit — protect the number and the inference budget
if (!(await SimpleRateLimiter.check(this.env.RATE_LIMIT_KV, input.from))) {
await this.reply(input, "You're moving fast! Try again in a minute.");
return;
}
// 2. Detect language on first contact, remember it for the session
if (!this.state.language) {
this.state.language = await this.detectLanguage(input.text);
}
// 3. Advance the flow: giveaway → name → company → use case →
// team size → timeline → demo booking
const next = await this.advanceFlow(input);
// 4. Persist session state
await this.saveState();
// 5. Reply on the channel the attendee used
await this.reply(input, next.prompt);
// 6. Qualified + demo requested? Page sales in real time.
if (next.qualified && next.demoRequested) {
await this.env.TELNYX.messages.send({
to: this.env.SALES_TEAM_NUMBER,
from: this.env.TELNYX_NUMBER,
text: `🔥 Hot lead: ${this.state.name} @ ${this.state.company} — ` +
`${this.state.useCase}, team of ${this.state.teamSize}, ` +
`timeline: ${this.state.timeline}. Demo requested.`,
});
}
}
}
Conversation state belongs to the attendee's SponsorAgent actor. KV has a separate job: it stores per-identifier rate-limit counters for SMS, WhatsApp, voice events, and browser chat.
Language detection and Q&A with Inference
Language detection is a single Inference call, and the same completion endpoint powers product Q&A:
async detectLanguage(text: string): Promise<string> {
const res = await this.env.TELNYX.ai.openai.chat.createCompletion({
model: this.env.AI_MODEL ?? "moonshotai/Kimi-K2.6",
messages: [
{ role: "system", content: "Detect the language. Reply with the ISO 639-1 code only." },
{ role: "user", content: text },
],
});
return res.choices[0].message.content.trim().slice(0, 2);
}
Because inference is available through the same Telnyx binding used by the Edge application, the working messaging and browser-chat paths stay compact: receive a message, load actor state, call the model, and reply.
Lead capture with SQLDB upserts
Every turn upserts the lead keyed on phone number, so a partially-qualified attendee who wanders off and comes back later resumes instead of duplicating:
await this.env.LEADS_DB.prepare(`
INSERT INTO leads (phone, name, company, use_case, team_size, timeline, stage, channel, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(phone) DO UPDATE SET
name = excluded.name, company = excluded.company,
use_case = excluded.use_case, team_size = excluded.team_size,
timeline = excluded.timeline, stage = excluded.stage,
channel = excluded.channel, updated_at = excluded.updated_at
`).bind(/* session fields */).run();
Scheduled follow-ups and attribution
Post-event follow-up is scheduled directly on the actor — no external scheduler:
this.schedule(eventEnd - Date.now(), "postEventFollowUp", {
phone: this.state.phone,
channel: this.state.preferredChannel, // SMS | WhatsApp | email | voice
});
And the attribution report is a plain SQL query over the same database:
const report = await env.LEADS_DB.exec(`
SELECT channel, stage, COUNT(*) AS count
FROM leads GROUP BY channel, stage ORDER BY count DESC
`);
Setup
- Clone the repo:
git clone https://github.com/team-telnyx/telnyx-code-examplesand openevent-sponsorship-agent. - Set environment variables:
| Variable | Purpose | Default |
|---|---|---|
EVENT_NAME | Name injected into agent prompts and the microsite | — (required) |
TELNYX_API_KEY | Your Telnyx API key | — (required) |
SALES_TEAM_NUMBER | Destination for hot-lead SMS routing | — |
AI_MODEL | Inference model for language detection and Q&A | moonshotai/Kimi-K2.6 |
DEMO_MODE | "true" dry-runs all outbound sends | "true" |
- Declare bindings in
telnyx.toml:SPONSOR_AGENT(ActorNamespace),RATE_LIMIT_KV(KvNamespace),LEADS_DB(SqlDatabase), theSECRETSbinding, and theTELNYXAPI binding. See the repo for the exact config. - Deploy the Edge function and attach your custom domain in the Telnyx console (e.g.,
yourbrand-at-theevent.com). - Point your messaging webhooks at
/webhook/smsand/webhook/whatsappon the deployed URL./webhook/voiceis available for extending the sample with a full Call Control flow. - Test safely.
DEMO_MODEdefaults to"true", so you can run the full flow — giveaway, qualification, hot-lead routing — without sending real messages. Flip it to"false"when the booth opens.
Conclusion
One Edge function can serve the event site, keep each attendee's conversation state, answer over messaging and chat, capture structured leads, schedule follow-up, and get sales involved while a promising attendee is still nearby.
The voice route is deliberately a boundary rather than a hidden promise: webhook verification and actor routing are present, while the live Call Control conversation is left for the next iteration. That makes the sample useful both as something developers can run today and as a foundation they can extend for their own event workflow.