Supporting Exchange and beyond · Brendan Abolivier
Some guy sharing his knowledge and thoughts with whoever wants to read it.
Brendan Abolivier
Built with Hugo, powered by Hyde
Source
Supporting Exchange and beyond
Mon, Jun 8, 2026<br>Hey there, me again!
This is the second and last part in a duology around Thunderbird’s project to support Microsoft Exchange. About a month ago, I published a blog post that shared some of the project’s behind the scenes (as well some of my personal thoughts), in which I was focusing a lot on the high-level picture and how we defined a direction for the project.
However, that article covers discussions that happened mostly around the start of the project, and didn’t get very technical. Although some of these conversations might have spanned over a longer period of time, the more practical, day-to-day progress of the project isn’t really mentioned - this is what this second part is for!
We’ve already looked at where we were going, now let’s focus on how we’re getting there.
Laying the foundations
Our first task was to ask ourselves what code infrastructure we were missing, and although there was already some functionality available to us in C++, on the Rust side of things (where our protocol client was going to live) things kind of looked like the barest of wastelands. After all, this was the very first time we were going to write any Rust specifically for Thunderbird.
We started by defining a baseline for what our protocol client needed to do. EWS traffic consists of of XML (SOAP) requests sent over an HTTP(S) connection, we therefore needed the ability to:
send out network traffic, and do so asynchronously
build HTTP request and receive responses
serialise serialise request data into XML, and deserialise XML responses
In addition, we needed whatever solutions to those requirements to be low on boilerplate. EWS lists many operations and even more types, and by this point we didn’t have such a precise idea of how many of those we would need.
Native asynchronicity
Let’s start by looking at the first point on that list (or at least part of it): performing asynchronous operations. Like I mentioned in the first part of this series, asynchronous operations in Thunderbird rely heavily on callbacks functions to be called whenever a change occurred with an operation. On the other hand, Rust uses an async/await syntax somewhat similar to what also exists in JavaScript or Python, so we’ll need a way to link the two together.
A long-standing issue of compatibility between Rust and C++ has been the lack of a stable ABI on either side making it difficult to carry types more complex than what the C ABI supports over the language boundary. A few solutions exist to address this, cxx being a pretty well-known and generic one. In our case, however, we’ll use something called XPCOM (Cross-Platform Component Object Model), which is a custom Mozilla cross-language compatibility layer used specifically by Firefox and Thunderbird.
XPCOM uses interfaces written in an Interface Description Language called XPIDL. Interfaces defined this way can be implemented in either JavaScript, C++ or Rust, and once registered with a contract ID (a unique human-readable string, e.g. @mozilla.org/my-component) each implementation can also be instantiated from any of these languages (note that XPIDL also defines attributes that can constrain which language can implement or use a given interface).
In our case, the interface we’re interested in is called nsIChannel (fun fact: a number of interface names in both Firefox and Thunderbird start with ns, which stands for “Netscape”, the company the Mozilla project originated from). In Mozilla terminology, a channel represents a resource fetch, which can be performed both synchronously and asynchronously.
This is interesting to us, because Necko, Firefox’s networking component which we also inherit, uses channels to perform HTTP requests. We could use an off-the-shelf HTTP crate like reqwest, but using Necko comes with a couple of main advantages:
requests and responses can be visualised in Thunderbird’s devtools (which is another thing we inherit from Firefox)
requests sent through Necko adhere to users’ network and web content settings, such as cookie settings, without specific support needed on the Thunderbird side
When when a channel is “opened” asynchronously, it takes an nsIStreamListener as its listener. This is an interface with three methods (two of them inherited from nsIRequestObserver):
OnStartRequest, which notifies that the operation has started
OnDataAvailable, which notifies that new data for the operation is available, and is called with a stream that contains this new data
OnStopRequest, which notifies that the operation has stopped, and is called with a status code indicating whether the operation has succeeded...