equoter
v0.1.0
Published
Identify quoted text in email messages (TypeScript port of quotequail)
Maintainers
Readme
equoter
Note: This codebase was mostly written by AI (Claude), with human direction and review.
There is an additional private test set based on real emails (not included in this repo for privacy reasons).
A TypeScript library that identifies and separates quoted text in email messages. Detects replies, forwards, and quoted blocks across multiple email clients and languages.
TypeScript port of quotequail, with additional improvements inspired by Mailgun's Talon.
Features
- Plain text & HTML — works with both
text/plainandtext/htmlemail bodies - Client-specific detection — fast-path selectors for Gmail (
div.gmail_quote), Outlook (#divRplyFwdMsg, border styles), Outlook Web App (#OLK_SRC_BODY_SECTION), Zimbra (hr[data-marker]), and more - Two-phase HTML detection — tries client-specific CSS selectors first, falls back to line-based pattern matching
- Multi-language — English, Dutch, German, French, Spanish, Russian, Swedish, Portuguese
- Email clients — Gmail, Apple Mail, Outlook (2003–2013, Web, iOS), Thunderbird, Mail.ru, Zimbra, Sparrow
- Lightweight — single runtime dependency (linkedom)
Install
pnpm add equoter
# or
npm install equoterUsage
quote(text, options?)
Split a plain text email body into quoted and unquoted parts.
import { quote } from "equoter";
const result = quote(`Hello world.
On 2012-10-16 at 17:02, Someone <[email protected]> wrote:
> Some quoted text`);
// [
// [true, "Hello world.\n\nOn 2012-10-16 at 17:02, Someone <[email protected]> wrote:"],
// [false, "\n> Some quoted text"]
// ]Each tuple is [shouldExpand, text] — true means original content, false means quoted.
Options:
| Option | Default | Description |
|--------|---------|-------------|
| limit | 1000 | Auto-quote everything after this many lines if no pattern is found |
| quoteIntroLine | false | Include the "On ... wrote:" line in the quoted portion |
quoteHtml(html, options?)
Same as quote() but for HTML email bodies. Uses a two-phase approach:
- Phase 1 — Try client-specific CSS selectors (Gmail, Outlook, Zimbra, etc.) for fast, reliable detection
- Phase 2 — Fall back to line-based pattern matching
import { quoteHtml } from "equoter";
const result = quoteHtml(`<div>Reply text</div>
<blockquote>On Jan 1, Someone wrote:<br>Original message</blockquote>`);Accepts the same options as quote(). Returns [shouldExpand, htmlFragment][].
unwrap(text)
Decompose a plain text email into structured parts.
import { unwrap } from "equoter";
const result = unwrap(`Hello
---------- Forwarded message ----------
From: Someone <[email protected]>
Date: Fri, Apr 26, 2013 at 8:13 PM
Subject: Weekend classes
To: [email protected]
Learn something new`);
// {
// type: "forward",
// text_top: "Hello",
// from: "Someone <[email protected]>",
// date: "Fri, Apr 26, 2013 at 8:13 PM",
// subject: "Weekend classes",
// to: "[email protected]",
// text: "Learn something new"
// }Returns null if no forwarded/replied/quoted structure is detected.
Return fields:
| Field | Description |
|-------|-------------|
| type | "reply", "forward", or "quote" |
| text_top / text_bottom | Text before/after the quoted content |
| text | The unwrapped message body |
| from, to, cc, bcc, subject, date, reply-to | Parsed headers (when present) |
unwrapHtml(html)
Same as unwrap() but for HTML email bodies. Returns html_top, html_bottom, and html instead of their text_ equivalents.
import { unwrapHtml } from "equoter";
const result = unwrapHtml(htmlEmailString);
// {
// type: "forward",
// html_top: "<div>Some intro text</div>",
// from: "Someone <[email protected]>",
// subject: "The subject",
// html: "<div>The forwarded content</div>"
// }Supported reply patterns
| Language | Pattern |
|----------|---------|
| English | On [date], [name] wrote: |
| Dutch | Op [date] schreef [name]: / [name] schreef op [date]: / Op [date] heeft [name] het volgende geschreven: |
| German | Am [date] schrieb [name]: |
| French | Le [date] a écrit : |
| Spanish | El [date] escribió: |
| Russian | [name] написал(а): |
| Swedish | Den [date] skrev [name]: |
| Portuguese | Em [date] escreveu: |
Differences from quotequail
- Function names use camelCase:
quote_html->quoteHtml,unwrap_html->unwrapHtml - Options are passed as an object:
quote(text, { limit: 500, quoteIntroLine: true }) - Uses linkedom for HTML parsing instead of lxml
- Two-phase HTML detection with client-specific CSS selectors (inspired by Talon)
- Additional Dutch reply patterns (
schreef op,heeft ... het volgende geschreven:) - Outlook
#divRplyFwdMsg, Outlook Web App, Zimbra, and Windows Mail detection
License
MIT — see LICENSE.
Based on quotequail by Elastic Inc. (Close.io), also MIT licensed.
