Workflow AutomationOperationsAIn8n

Stripping Quoted Text From Email Replies Without Regex

Most automations strip quoted email history with a regex, but the correct method depends on where the mail comes from. Microsoft Graph returns a reply-only body in uniqueBody, Mailgun returns stripped-text, and Postmark returns StrippedTextReply, while the Gmail API and SendGrid Inbound Parse return the full body and leave the work to you. Check which tier your mail source supports before writing any parsing code.

Alexey YushkinFounder, GENERAL INFORMATICS3 min read

Before you write a regex to strip quoted history out of an email reply, check what your mail source already gives you. Microsoft Graph returns a reply-only body in a property called uniqueBody. Mailgun's inbound routes post a stripped-text field. Postmark's inbound webhook posts StrippedTextReply. The Gmail API and SendGrid's Inbound Parse webhook return the full body and nothing else, so those two, along with raw IMAP forwarding, are where you actually have to parse it yourself. The problem is source-dependent, not code-dependent, and almost every guide on the topic opens with the regex.

What breaks when the quoted history survives

The visible symptom is cosmetic. A ticket comment or CRM note that should be one sentence long instead contains the entire conversation, again, for the eleventh time.

The expensive symptom is not cosmetic. When you hand that body to a model to classify, summarize, or draft a reply, the model reads the whole thread as one block of current text. The oldest message in the chain is often the one with the clearest question in it, because that is where the customer first explained what they wanted. So the assistant answers the question from message one, which the customer resolved themselves in message three, and the reply goes out sounding like nobody read anything. We have debugged this exact behavior more than once, and the prompt is never the problem. The same leftover history is also why an assistant can reply in English to a customer who wrote in Spanish: your own quoted English outweighs their new message.

Cost is the third one. A quoted block is re-sent on every message in the chain, so a ten-message thread where each new reply is 60 words carries roughly 600 words of history against 60 words of content by the end. The HTML part is worse, since it carries the container markup and inline styles of every client that touched the thread. If you are paying per token to summarize inbound mail, thread depth is what your bill actually tracks.

Check your mail source before you write a parser

Here is the part that gets skipped. Three of the six sources below already compute a reply-only body and three do not, and which one you are on decides the entire design.

Mail sourceReply-only body availableField to read
Microsoft Graph (Outlook, Exchange Online)YesuniqueBody, requested with $select
Mailgun inbound routesYes, best effortstripped-text, stripped-html, stripped-signature
Postmark inbound webhookYes, conditionalStrippedTextReply
Gmail APINopayload parts only
SendGrid Inbound ParseNotext and html only
Raw IMAP or MIME forwardingNothe whole message, as sent

The Microsoft row is the one worth reading twice. Graph's documentation describes uniqueBody as the part of the body of the message that is unique to the current message, and states plainly that it is not returned by default and has to be retrieved with a ?$select=uniqueBody query. That default is why the property is invisible in practice. A developer calls the messages endpoint, sees body and bodyPreview in the response, concludes that Graph does not offer anything better, and writes a splitter. Exchange computed the answer already and was waiting to be asked for it.

If you are on Gmail or SendGrid, you have real work to do. If you are on Graph, Mailgun, or Postmark, the work is a field name.

The blind spots are documented, and they are all different

None of the three vendor strippers is a black box you can ignore. Each publishes exactly where it stops working, and the failure shapes are not the same, which matters because your fallback has to match the shape.

Mailgun fails by omission. Mailgun makes a parsed version of each text body, and when parsing fails, the stripped-* fields are simply not present in the payload. There is no error, no flag, no empty string. Mailgun also notes that badly-constructed HTML is a common cause. If your code reads stripped-text with an empty-string default, a parse failure hands your automation a blank body and it proceeds cheerfully to create a ticket with no content in it. Treat a missing field as a branch, not a default.

Postmark fails by precondition. Postmark documents three conditions on StrippedTextReply. The inbound message has to be a reply that included either an In-Reply-To or a References header. There has to be a plain text part, because Postmark cannot parse HTML parts of replies for this field. And the field is limited to English text replies. Postmark also lists the clients it has tested against, including Gmail, Outlook.com, Apple Mail, iOS Mail, Yahoo, iCloud, Microsoft Outlook on Windows and Mac, and Thunderbird. A first inbound message that is not a reply at all will legitimately have no StrippedTextReply, and that is correct behavior rather than a bug.

Microsoft fails by silence. uniqueBody does not throw when you forget to select it. It is just absent from the response, which reads identically to an empty message body if you are not checking.

The pattern across all three: absence is ambiguous. Write the branch that distinguishes "the stripper ran and found nothing new" from "the stripper did not run."

A delimiter you control beats every parser

For mail you send yourself, there is a tier above all of this, and helpdesk vendors have used it for years. You put a known string in the outbound message and split on that exact string when the reply comes back. Zendesk's default is the line ##- Please type your reply above this line -##, inserted through a {{delimiter}} placeholder that has to sit before {{content}} in the email template. Zendesk describes it as a line of text telling the recipient that any text entered into a reply must be above a certain line.

This works because you are not guessing at another client's markup. You are matching a string you wrote. It is the right choice for reply-by-email flows: quote follow-ups, appointment confirmations, anything where your system sent the message that the customer is replying to. If you are building lead capture and follow-up that runs over email, put the delimiter in from day one rather than retrofitting a parser later.

It has two honest limits. Someone will reply underneath the line, and someone else will forward a thread in from a mailbox you never sent to, at which point there is no delimiter to find. Both fall through to the tiers above.

When regex is genuinely the only option

On Gmail or SendGrid, with mail you did not originate, you are parsing. Accept that you are writing something client-specific and treat it that way.

Only one marker in the whole space is even semi-standard, and it is for signatures rather than history. RFC 3676 describes -- as a long-standing convention in Usenet news that also commonly appears in Internet mail as the separator between the body and the signature. Note the word convention. It is not a requirement, plenty of clients do not emit it, and Outlook in particular is not reliable here.

For quoted history there is no standard at all. Every client wraps it in its own container in the HTML part and its own attribution line in the text part, which is why an off-the-shelf splitter is really a bundle of per-client rules with a maintenance cost attached.

Three rules make the parsing survivable:

  1. Parse the plain text part when one exists, not the HTML. Text quoting is messier in appearance but far more stable across clients than HTML container markup, which changes when a vendor ships a redesign.
  2. Never send an empty stripped body downstream. If the split produces nothing, pass the full body forward with a flag on it, and let the human or the model see everything rather than nothing.
  3. Log the pre-split and post-split lengths on every message. A parser that quietly starts returning the full body after a client update looks identical to a parser that is working, until you look at that ratio. This belongs in the same routine as everything else you should be logging on every automation run.

If a model is reading the result, add one more guard. Tell it in the prompt that the input may still contain quoted history and that only the newest message is current. It is not a substitute for stripping, and it does nothing for your token bill, but it turns the worst failure mode into a merely imperfect one. That guard belongs in the assistant and agent layer, not in the mail parser.

Where to start this week

Open your inbound mail integration and answer one question: which of the six sources in that table are you actually on? That single answer tells you whether this is a five-minute field change or a parser you need to own and maintain.

If the answer is Microsoft Graph, add $select=uniqueBody to the call, compare the result against the body you have been using on a handful of live threads, and delete the splitter. If the answer is Mailgun or Postmark, keep the stripped field but add the branch for when it is missing, because the version you have now is almost certainly reading it with a default. If the answer is Gmail, SendGrid, or raw IMAP, add the length-ratio log first and pick your parsing strategy second, so you can tell whether it is working a month from now.

If your inbound mail is feeding an AI step and you want a second pair of eyes on where the thread history is getting in, tell us what the flow looks like and we will tell you which tier you are on.

Frequently Asked Questions

Check whether your mail source already does it before writing any code. Microsoft Graph exposes uniqueBody, Mailgun's inbound routes post stripped-text and stripped-html, and Postmark's inbound webhook posts StrippedTextReply. The Gmail API and SendGrid's Inbound Parse webhook give you the full body only, so those are the cases where you need your own parsing.

Because it is not returned by default. Microsoft's documentation states that uniqueBody is not returned by default and has to be requested with a $select=uniqueBody query. Teams routinely never discover the property exists, write a regex against the body property instead, and ship a parser they did not need.

Postmark documents three conditions. The message must be a reply carrying either an In-Reply-To or a References header, there must be a plain text part because Postmark does not parse HTML parts for this field, and the field is limited to English text replies. A reply that is HTML only, or that arrives without those headers, produces no StrippedTextReply at all.

Yes, in two ways. The model reads the oldest message in the thread as if it were current, which is how an assistant ends up answering a question the customer already resolved four replies ago. Cost also grows with thread depth, because the quoted block is re-sent on every message and the HTML version carries markup on top of the text.

It is, for mail you send yourself. Zendesk's default delimiter is the line '##- Please type your reply above this line -##', and the split is reliable because you control the exact string on the way out. It does not help with mail you did not send, and it fails when someone replies underneath the line or forwards the thread in from somewhere else.

Only for signatures, and it is a convention rather than a requirement. RFC 3676 describes the '-- ' separator as a long-standing convention in Usenet news that also commonly appears in Internet mail. There is no equivalent cross-client standard for where quoted history begins, which is why regex-based splitters are client-specific by nature.

SOURCES & CITATIONS

  1. message resource type (Microsoft Graph v1.0) — Microsoft Learnhttps://learn.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0
  2. Receive, forward and store messages: HTTP route actions — Mailgunhttps://documentation.mailgun.com/docs/mailgun/user-manual/receive-forward-store/receive-http
  3. Parse an email: inbound message details — Postmarkhttps://postmarkapp.com/developer/user-guide/inbound/parse-an-email
  4. Gmail API reference: users.messages resource — Googlehttps://developers.google.com/workspace/gmail/api/reference/rest/v1/users.messages

About Alexey Yushkin

Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.

Connect on LinkedIn

Related reading

Want this kind of system in your business?

We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.