email-reply-parser
Parses plain-text email replies to strip quoted history and signature blocks, leaving just the visible reply text.
Repository Health
Technical Analysis
email-reply-parser is a Node.js library that separates the visible reply from quoted history and signature blocks in plain-text email bodies. It reverses each message line-by-line to detect quote headers, signature separators, and forwarded-message markers across roughly a dozen locales, then reassembles the content into ordered fragments each flagged as quoted, hidden, or visible.
At Crisp, where it processes around a million inbound emails, the library exposes a small API — parseReply() for the human-written text and parseReplied() for the quoted portion — while optionally using the RE2 engine to guard against catastrophic backtracking (ReDoS) in its many locale-specific regular expressions, falling back transparently to native RegExp when RE2 isn’t installed.
What You Get
- A single EmailReplyParser class with read(), parseReply(), and parseReplied() methods for extracting visible or quoted text
- Locale-aware quote-header detection covering roughly ten languages including English, French, German, Spanish, Italian, Portuguese, Japanese, Korean, Polish, and Chinese
- Signature-line stripping for common client-generated sign-offs like “Sent from my iPhone” and “Best regards”
- Optional RE2 regex engine integration for ReDoS-resistant parsing, with automatic fallback to native RegExp
- Full TypeScript typings shipped alongside the compiled ESM output
Common Use Cases
- Helpdesk reply threading - support platforms like Crisp use it to show agents only the customer’s newest reply instead of the entire quoted history
- Email-to-ticket ingestion - ticketing systems parsing inbound email webhooks strip signatures and quoted replies before storing ticket comments
- Chat/inbox unification - products that turn email replies into chat messages need just the visible text to render in a conversational UI
- Conversation summarization - systems that digest email threads rely on the visible-text extraction to avoid re-processing already-seen quoted content
Under The Hood
Architecture EmailReplyParser (lib/emailreplyparser.ts) is a thin facade that delegates to EmailParser.parse() in lib/parser/emailparser.ts, which reverses the full input text, splits it into lines, and walks them bottom-up building FragmentDTO objects (lib/parser/fragmentdto.ts) grouped into quoted/signature/visible fragments using pattern matching against the RegexList singleton (lib/regex.ts). Each fragment’s lines are then reversed back to natural order and wrapped in immutable Fragment instances (lib/fragment.ts) inside an Email aggregate (lib/email.ts) that exposes getVisibleText()/getQuotedText() by filtering on fragment flags. The pipeline is small and single-purpose — parse, aggregate, query — with minimal abstraction; the one extension point is setQuoteHeadersRegex(), which lets a caller mutate the shared regex list at runtime, though swapping the reversal-based line algorithm itself would mean rewriting EmailParser.parse() nearly wholesale.
Tech Stack The library is written in TypeScript and compiled via tsc to ESM output (Node16 module resolution, es6 target) with generated .d.ts typings; it has no required runtime dependencies beyond an optional peer dependency on RE2, a native regex engine loaded defensively behind a try/catch require() call. Development tooling includes the typescript-eslint toolchain plus project-specific eslint-plugin-crisp and eslint-plugin-jsdoc rules, with nodeunit as the test runner. The package targets Node 22 and above and ships dual import/require export conditions pointing at the same compiled output — an intentionally minimal footprint for a pure text-parsing library.
Code Quality The test suite runs via nodeunit against an extensive set of real-world fixture emails covering numbered generic cases plus named locale and edge-case scenarios — French, German, Portuguese, Italian, Finnish, Russian, Danish, Polish, Korean, and Japanese variants, alongside tricky cases like URLs containing “wrote”, nested quotes, double signatures, and HTML entities. This is a strong regression net for a regex-heavy parser where naive rules easily produce false positives. Error handling is minimal by design since the library performs no I/O; naming is consistent and every public method carries a JSDoc comment enforced by the linter. Continuous integration runs both the test suite and lint pass on every push via GitHub Actions.
API Design The public surface is deliberately tiny — one class with three methods — so the common case reduces to a single call: new EmailReplyParser().parseReply(text). Method names read naturally at call sites, and the underlying Fragment/Email split gives an escape hatch for consumers who need more than the two built-in convenience filters. The README documents the API with a runnable example and explicit guidance on the optional RE2 dependency. The main friction point is that setQuoteHeadersRegex() mutates a shared singleton rather than being scoped per instance, so callers wanting different header rules per invocation have no clean way to isolate that state.