I'm writing a Rust microservice to render a contact form, accept submissions, handle some custom business logic that's not relevant to this post, and ultimately send out emails. I'm using lettre to render and send the emails via SMTP, but I'm not sure how best to write integration tests covering my lettre code. I would love any advice on this. Thank you in advance!
I'll include my thoughts on how to solve it below, but in XY problem terms, the above is the Y and everything below is the possibly unhelpful X. The options below are so divergent that I thought it would be wise to ask for help before picking a path. I'm leaning toward idea 3, since it's pure Rust, but I'll certainly admit that idea 1 might be a lot less work.
Idea 1: follow lettre's lead
I notice in the Testing section of the lettre README that lettre's own tests use Python's smtpd, specifically the DebuggingServer
class. However, the smtpd module has been deprecated and removed. Python recommends switching to aiosmtpd, which appears to lack equivalent functionality. (I have only spent a few minutes looking; it's very possible that I might have missed something.)
I'm fairly uncomfortable with the idea of depending on Python code for my Rust tests, though I'm willing to do so if it's the best way. I'm even more uncomfortable with the idea of deliberately running an outdated version of Python for this purpose. Having said that, I'm already running Postgres via a container, so I can always build a container to isolate the now-legacy Python code, if that's the best solution.
Idea 2: containerized email server
I know email infrastructure terminology just well enough to know that "server" might not quite be the right term but not well enough to be sure of the right term. (MTA?) In any case, I could conceivably run something like Exim, Postfix, etc. in a container; configure it with testing credentials; send emails there via lettre; and check it using one of the methods below.
Of course, this whole idea seems like it's probably about ten times more complex than necessary, which seems to point me back toward containerized Python 3.11 with smtpd.
Option 2(a): IMAP client
I could use a crate like async-imap or email-lib to check the emails.
Option 2(b): POP3 client
I am not aware of any POP3 client crates, but POP3 is so simple that I'm guessing I could implement the necessary subset just fine. I'm assuming the biggest headaches will revolve around security.
Idea 3: Rust crates mailin, mailin-embedded, samotop, or mailtutan
Of these, mailin-embedded seems like it's probably the most obvious choice:
- Implement a mailin_embedded::Handler that accepts emails to the satisfaction of lettre, possibly parses them, and sends values back to the test via a channel (probably a tokio::mpsc variant)
- In integration tests, start the server in its own thread (presumably via tokio::task::spawn_blocking) and poll for server start
- Send emails, await data from channels (perhaps with timeout), and assert received values