Let Each Tool Do Its Job
I like building software that connects tools people already use. Thunderbird handles email accounts. Hermes helps with research and writing. Rust gives us a reliable place to manage the workflow between them.
We recently brought those pieces together with a small Thunderbird extension and a Rust native messaging bridge. The application prepares work for review, and Thunderbird sends approved messages through its existing account configuration.
No separate mail-provider login inside our application. No additional HTTP server for the bridge.
Hermes Handles the Assistance
Our Rust application connects to a Hermes gateway through rs-hermes. We supply instructions,
relevant context and the structure we expect back.
Hermes can assist with research and message preparation. Rust validates the response and saves it for review. Generating a message does not authorise sending it.
That distinction keeps the workflow straightforward:
- Prepare the content.
- Validate and save it.
- Let someone review and approve it.
- Execute the approved action.
- Record the result.
Prompts and models can change without moving approval rules into a conversation with the model. Those rules stay in application code.
Thunderbird Already Knows How to Send Email
Thunderbird already manages identities, passwords, outgoing SMTP servers and Sent-folder preferences. Reusing that configuration saves us from maintaining another set of mail credentials.
An account can receive through one provider and send through a separately configured SMTP service. Our application selects the Thunderbird identity, and Thunderbird uses that identity's outgoing server.
Before sending, we require exactly one identity matching the requested sender address. Missing account? Stop. Two matching identities? Stop and resolve the ambiguity.
Silently choosing another account would be a rather unpleasant surprise.
A Small Native Messaging Bridge
Thunderbird extensions can communicate with a local executable through native messaging. Our extension launches a Rust host and exchanges JSON messages over standard input and output.
A local SQLite queue connects that host to the application. The extension polls for work, performs the requested operation and returns its result.
- Hermes: research and content assistance.
- Rust: validation, approvals, queue state and delivery history.
- The extension: identity selection and compose operations.
- Thunderbird: credentials and SMTP delivery.
The native host registration permits our specific extension ID. There is no additional listening port to configure.
Check the Message Before Sending It
The extension prepares the message using Thunderbird's compose API. It checks the recipients, subject and content against the submitted request before asking Thunderbird to send.
Our HTML template and Bcc carry through the bridge. Thunderbird generates the outgoing message, including the text alternative, and follows the identity's Sent-copy settings.
Drafts remain in our application. This integration handles account checks and sending; it does not synchronise the inbox or import replies.
A Timeout Does Not Mean “Try Again”
An SMTP server can accept a message before confirmation reaches the application. If the connection then fails, immediately retrying might send the same email twice.
Our bridge dispatches each queued job once. Restarting it does not replay dispatched work. An unclear outcome becomes an uncertain delivery that needs reconciliation.
Successful sending records server acceptance. It does not pretend to prove that the message reached someone's inbox.
Useful Automation Still Needs Ordinary Engineering
The first use is business correspondence, but the same approach could support internal notifications, document delivery and other reviewed communication workflows.
We build software applications, AI integrations and software that connects your business and lets you operate at scale. Tell us what you would like to connect.
Happy Hacking!
