Skip to main content

Story: build a real-time chat monitor

A support chat that wakes a human for every message will not survive its first launch week. This build puts machinery between the customer and your team: every message lands on a topic, a function classifies it in flight, an agent answers the routine ones, and the rest reach humans with the conversation already summarized. A dead-letter topic catches whatever the machinery could not, so nothing silently disappears.

This story combines five services — Pub/Sub, Functions, Agents, Secrets, and the Gateway — and it leans on two tutorials you can treat as the appendix: event-driven functions for the delivery mechanics and event-driven functions' dead-letter section for the parking lot.

The shape of the thing

Why a topic, and not a direct call

The tempting version of this system is one function calling the next function. The version above puts a Pub/Sub topic between every pair, and here is what that buys:

  • Retries stop being your code. A push subscription redelivers until your function acknowledges. If the triage function is mid-deploy when a message arrives, the message waits — customers do not see your deploy.
  • The dead-letter topic is the insurance policy. A message that fails every retry is parked, not lost. You read the parked messages on a pull subscription and decide what to do, in daylight, with the bad message in hand. That is the difference between "we had a bug" and "we lost your chat."
  • Fan-out is free. Later, when you want an analytics copy of every message, you add a second subscription to the same topic. Each subscription keeps its own reading position, so the new reader never steals from triage. No rework of the intake path.
  • The budget is explicit. A topic claims its max_bytes from your project's storage budget the moment it exists — empty topics spend budget. Four small topics are not free. Read topics and subscriptions before you size them; surprise 409s come from this, nowhere else.

The cast

PieceWhat it isIts one job
intakeFunction behind a Gateway endpointAccept the customer's message, publish it to chat-events, answer "got it" fast
chat-eventsPub/Sub topicBuffer every message, retry delivery, park the unhandleable
triageFunction on a push subscriptionClassify: routine or escalate
support-agentAgentAnswer the routine ones, with sessions so follow-ups work
escalationsTopicHand the rest to humans, message plus agent's summary
chat-events-deadTopic + pull subscriptionHold what nothing could deliver, until you arrive

And one quiet actor: the customer's API key lives in a secret, injected into the functions as environment variables. No key in source control, no key in a deploy command.

The two seams to get right

The acknowledgement deadline is your latency budget. A push subscription gives your function 30 seconds to acknowledge before it redelivers. If your triage function itself invokes the agent and the agent's cold start eats 20 of those seconds, you will redeliver — and re-invoke — under exactly the conditions you least want duplicates. The honest fix: triage should acknowledge fast and publish forward — classify, publish to the next topic, return. Let the agent's work happen behind its own topic with its own deadline.

The agent's summary is the handoff. When triage escalates, the human who picks up the escalation message should not have to ask "what did the customer say?" Publish the escalation with the conversation so far in the body. On this platform that is cheap: the agent's session is already the assembled history, so the escalate-branch of triage asks the agent for a one-paragraph summary and publishes {customer, summary, original_message} in one message.

The build order that works

Build it backwards from the failure:

  1. Create the three topics firstchat-events, escalations, and chat-events-dead — because topic creation is admin-only and the storage budget check happens there. Get the 409s before there is traffic.
  2. Deploy triage and support-agent next. They are the consumers; them existing is what makes the subscriptions healthy.
  3. Wire the push subscription from chat-events to triage, with the dead-letter topic attached. Event-driven functions shows both shapes — a managed trigger for the simple case, a hand-built subscription when you need dead-lettering. You need dead-lettering, so build the subscription.
  4. Deploy intake and publish it behind a Gateway endpoint with --auth none if it is truly public, or an API key if it is your own widget. Whatever you choose, the endpoint fails closed: if the protection cannot be applied, nothing serves.
  5. Break it on purpose. Publish a message your triage cannot parse, watch it retry, watch it park in chat-events-dead, and read it back with the pull subscription. If you only test the happy path, the dead-letter topic is decoration.

What you just used, and where to read more

You usedThe page that documents it
Topics, subscriptions, budgetsTopics and subscriptions
Push delivery, retries, dead-letterEvent-driven functions
The function contractHTTP and events
Agents answering with contextAgents overview, Sessions
Public entryPublish an endpoint
The keys between servicesUse secrets in workloads