How a message gets its language detected
The relay can’t translate a message until it knows what language the message is in. That sounds trivial — and for a long, clearly-written paragraph it is. But real customer messages are short, full of order numbers, signed with a name in a third language, and sometimes written half in one language and half in another. This post walks through how a message gets in (two ways), how it gets cleaned down to the words that matter, and how the relay reads its language without guessing wrong on the hard ones.
Key takeaways
- Two ways in feed one thread: email through SES inbound and a web widget through a Function URL.
- Every message is cleaned first — signatures, quoted history, and footers are stripped before detection.
- A cheap first pass reads the language; a careful fallback handles short, mixed, or code-heavy notes.
- The detected language and a confidence read are saved on the thread so later turns can reuse them.
- If the relay still isn’t sure, the message is marked “language unclear” for a person, never guessed silently.
Two ways in, one cleaned message
Lane 1: email
The most common way in. You point your support address — or a forwarding copy of it — at the relay through Amazon SES. When a customer emails, SES writes the raw message to an S3 bucket, and that write triggers a small intake Lambda. The Lambda walks the email to its plain-text body (falling back to the HTML body, stripped to text, if there’s no plain-text part), figures out which conversation it belongs to from the sender and subject, and drops the new message onto that thread. If it’s a brand-new sender, it starts a fresh thread.
Email is messy. A reply often carries the entire history of the conversation quoted below it, plus a signature block, plus a mobile footer. Translating all of that would be wasteful and confusing, so the very next step is cleanup — covered in Lane 3.
Lane 2: the web widget
Some customers would rather not email at all. A small chat widget — one line of script on your site — lets a visitor type a question in their own language and get help right there. The widget posts the text to a Lambda Function URL (a plain web address that runs a Lambda, with no API Gateway in front of it), along with a thread id so the back-and-forth stays together. From there it joins the exact same flow as an email: same cleanup, same detection, same thread store. The customer doesn’t care which pipe their message took, and neither does the rest of the system.
The widget text is usually cleaner than email — no signature, no quoted history — but it’s often shorter, which makes language detection harder, not easier. A two-word message gives the detector very little to go on.
Lane 3: cleanup, then read the language
Whichever way the message arrived, the relay now has to find the words that matter and read their language. Cleanup comes first. A small deterministic step — plain Python, no model — strips the email signature, the quoted reply history (the lines that start with “>” or sit under “On Tuesday, X wrote:”), and footers like “Sent from my iPhone.” What’s left is the new text the customer actually wrote this time.
Then detection runs in two passes. The first pass is cheap: for a message of reasonable length, a fast script-and-statistics check reads the language confidently and for free. Most messages stop here. The second pass is the careful fallback, and it exists for the hard cases — a three-word reply, a message that mixes two languages, a note that’s mostly product codes and prices with only a few real words. For those, the relay asks Bedrock with Claude Haiku 4.5 to read the language, since a model handles short and mixed text far better than a statistics check. Each pass returns both a language and a confidence score, and both go onto the thread.
There’s one more rule, and it’s the important one: the relay never guesses silently. If even the careful fallback comes back unsure — say, a message that’s genuinely half English and half Spanish, or one too short to call — the message is marked “language unclear” and shown to a person, with the original text, before anything gets translated. A wrong language guess at this step poisons every step after it, so it’s the one place the system would rather stop and ask.
Why detection is its own step
It would be tempting to fold detection into the translate step — just hand the message to the model and say “translate this to English, whatever it is.” That works most of the time, but it hides the one fact you most want recorded: which language the customer is writing in. The relay needs that fact to translate the reply back later, to pick the right tone, and to reuse it on the customer’s next message so a one-word follow-up like “sí” doesn’t get mis-read. Saving the detected language on the thread, with its confidence, makes every later step cheaper and steadier.
Next post: how the cleaned message gets translated into your team’s language, shown side by side with the original, with anything the relay wasn’t sure about highlighted for a human to check.
All posts