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.

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

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.