Part 3 of 7 · Social inbox unifier series ~5 min read

How a DM gets labeled and deduped

A message is sitting on the work queue in the common shape. Two things have to happen before it’s ready for a teammate: drop it if it’s a copy of one already here, and tag it with a topic, an urgency, and a language so the inbox can sort and route it. The dedupe is plain code — a fingerprint and a quick lookup. The tagging is one short model call that reads the message and hands back a few labels. The model reads the message; it never answers it.

Key takeaways

  • Every message gets a fingerprint from its sender, platform, and a hash of the text.
  • A short-lived DynamoDB table remembers recent fingerprints; a repeat is dropped and linked, not re-opened.
  • One Bedrock Haiku 4.5 call returns topic, urgency, language, and a one-line summary — nothing else.
  • The model is asked for labels only. It never drafts or sends a reply.
  • Labeled, deduped messages move on to routing; duplicates just attach to the existing thread.

The decision flow, per message

Decision flow per message off the work queue A vertical decision flow diagram. At the top, an input box "Message off the queue" with the sender, platform, text, timestamp, and conversation id. Below that, a step "Compute fingerprint" — a hash built from the sender, the platform, and the message text. Below that, a check "Seen this fingerprint?" — read a short-lived DynamoDB dedupe table; if yes, route to "Duplicate" (link it to the existing thread and stop). If no, continue. The next step "Label with one Bedrock call" — Claude Haiku 4.5 reads the text and returns topic, urgency, language, and a one-line summary. The next step "Read the urgency label" — sort the message by the urgency the model returned. If the message is low priority, route to "Low." Otherwise look at the urgency level: a normal-priority message routes to "Normal," and a high-priority message — one with a deadline, a complaint, or money at stake — routes to "Urgent." Each terminal box — Duplicate, Urgent, Normal, Low — records the outcome and hands the message to routing with its labels attached, except Duplicate which simply links to the live thread. A note at the bottom: the model returns labels only; it never writes or sends a reply — a person does that, later. Message off the queue sender · platform · text · thread Step 1 Compute fingerprint hash(sender + platform + text) Step 2 Seen this fingerprint? read DDB dedupe table Step 3 Label with one Bedrock call topic · urgency · language Step 4 Read the urgency label low → low lane deadline/complaint → urgent Step 5 Attach labels, hand off write thread + labels to DDB Duplicate link to live thread Urgent top of the queue Normal standard lane Low when time allows if yes repeat low urgent normal The model returns labels only — it never writes or sends a reply. A person does that, later.
Fig 3. The labeler’s flow, per message off the queue. Fingerprint first, so a duplicate never opens a second thread; then one model call for labels; then sort by urgency and hand off to routing. The model describes the message — it never answers it.

Dedupe first, because a copy is worse than nothing

Duplicates are common, and they come from two everyday situations. The first is the platform re-sending the same webhook — if a connector is slow to reply for a moment, the platform assumes the call failed and tries again, so the exact same message arrives twice. The second is a customer who, getting no instant answer, messages you again on a different channel: the same person, the same question, on Instagram and on Facebook five minutes apart. Both should land as one thread, not two, because two threads means two teammates might both reply, or each assumes the other will.

So before anything else, the labeler builds a fingerprint: a short code computed from the sender, the platform, and a hash of the message text. (A hash is just a fixed-length stamp of the text — same text in, same stamp out.) It checks that fingerprint against a small DynamoDB table that remembers recent fingerprints for a short window — long enough to catch re-sends and same-day cross-channel repeats, short enough that a genuinely new question next month is never mistaken for an old one. If the fingerprint is already there, the message is a duplicate: it’s attached to the existing thread as “also messaged on Facebook” and goes no further. If it’s new, the fingerprint is recorded and the message continues.

One model call, for labels only

A new message then gets exactly one Bedrock Haiku 4.5 call. The prompt is short and strict: “Read this message. Return JSON only with four fields — topic (one of the configured topics), urgency (urgent, normal, or low), language, and a one-line summary. Do not write a reply. Do not suggest a reply.” A few hundred input tokens, a few dozen out. That’s the entire AI footprint of the system on the hot path.

The labels do real work downstream. topic drives routing — a refund question goes to billing, a shipping question goes to ops. urgency drives the sort order, so the message with a deadline rises above the one that just says “thanks.” language lets the inbox flag a Spanish message for a teammate who handles Spanish, or show a translation hint. The one-line summary is what a teammate sees in the queue list before opening the thread, so they can pick the right one to work first.

Why the model only labels — and never answers

This is the line the system never crosses. The model is allowed to describe a message; it is never allowed to respond to one. There are two reasons, and they reinforce each other. The first is trust: a customer-facing reply in your brand’s voice is something a person should own, word for word. A wrong auto-reply — a refund promised that policy doesn’t allow, a tone that misreads an upset customer — is far more damaging than a message that waited ten extra minutes for a human. The second is cost and simplicity: labeling is a tiny, predictable call, while drafting and checking replies would be a much larger, riskier loop. Keeping the model on labels makes the whole system cheap, fast, and easy to reason about.

What if a label is wrong

Labels are a starting point, not a verdict. The shared inbox shows the topic and urgency as editable tags. If the model called something “normal” that a teammate sees is clearly urgent, they bump it with one click, and the thread re-sorts and can re-route. Those corrections are logged. Over time they’re a useful signal about which topics the labeling tends to misjudge — the kind of thing you tune in the prompt or the topic list, not by adding more model calls. The point is that a human is always in a position to overrule the machine, on every message.

Next post: how a labeled, deduped message finds the right teammate — routing by topic, working hours, fair load-sharing, and a backup for when the right person is off.

All posts