Projects / deltachat_message_parser
The problem
Delta Chat runs on Android, iOS, and Desktop. Each platform had its own code for detecting links, email addresses, and other clickable elements in messages. Bugs got fixed on one platform but not another, and behavior was inconsistent.
The idea was to build a single parser in Rust that all platforms could share. We started by integrating it into Desktop, replacing a buggy regex-based library, by compiling it to WebAssembly and releasing it as an npm package. The plan was to integrate it into Delta Chat's Rust core once it proved stable enough, which would then make it easy to use it on the other platforms.
More than markdown
While the parser supports a markdown subset (bold, italic, strikethrough, inline code, code blocks), markdown is an extra feature, not the main goal. The core purpose is unified linkification: reliably detecting and making clickable all the things that should be clickable in a chat message:
- URLs (including schemes like
mailto:,tel:,bitcoin:,magnet:) - Email addresses (tapping opens a chat with that contact)
- Bot command suggestions (
/command) - Hashtags (used as search shortcuts)
The parser has two modes: a text-only mode for just linkification, and a full mode that adds markdown formatting on top.
How it works
The parser is built with nom, a parser combinator library for Rust. This was a deliberate choice over regex-based parsing: most link parsers (including Android's built-in one) use regular expressions, which can be vulnerable to ReDoS attacks with crafted input. nom's combinators don't backtrack in the way vulnerable regex engines do, so the specific catastrophic-backtracking patterns behind most ReDoS CVEs don't apply.
Inspired by how Discord handles rich messages, the output is an Abstract Syntax Tree (AST) rather than rendered HTML. This lets each platform's UI layer decide how to display elements using its native primitives: Android uses Spans, iOS uses NSAttributedString, and on the web the AST can be safely mapped to React elements, avoiding the XSS risk that comes with innerHTML.
The AST also enables interactive behavior beyond just rendering.
For example, a labeled link like [Click here](https://example.com)
triggers a confirmation dialog showing the actual URL to prevent
phishing. Internationalized domain names are shown in punycode for
the same reason.
What went wrong
The parser worked, but link parsing had too many edge cases and bugs. A key mistake was building the test suite from scratch instead of starting with an established one. Link parsing is deceptively hard, and without a comprehensive set of existing test cases to validate against, edge cases kept slipping through.
Nobody else stepped up to maintain the project, so fixes were slow. On the markdown side, there were (and still are) ongoing discussions about whether Delta Chat even wants or needs markdown support, or whether an inline editing UI would be a better approach.
In the end, Delta Chat Desktop switched to linkifyjs for link detection, the experimental markdown support was removed, and the message parser was frozen. The parser never reached the other platforms.
Lessons learned
If I were to revive this or start over, I would clone linkifyjs's test suite as a starting point rather than writing tests from scratch. Having a battle-tested set of edge cases from day one would have caught many of the bugs that ultimately made us give up.
The core idea remains sound: a single Rust parser with an extensive test suite, used by all platforms, that unifies parsing in one place. It would just need to start from a solid test foundation instead of assembling one bug report at a time. The project is currently dormant, but the WASM demo still works and shows the parser in action.