Day 22 - lettre
lettre
is a library to send emails over
SMTP from our Rust applications. Like many other crates in the growing,
but still young Rust ecosystem, it is still a work in progress. But while
there are a few features missing from lettre
, we can send some emails
right now!
Just send the email already
Let's start with something simple: send a plain Hello Rust email. Here's
our first approach with lettre
:
extern crate lettre;
use lettre::email::EmailBuilder;
use lettre::transport::EmailTransport;
fn main() {
let email = EmailBuilder::new()
.from("zbigniew@siciarz.net")
.to("zbigniew@siciarz.net")
.subject("Hello Rust!")
.body("Hello Rust!")
.build()
.expect("Failed to build message");
let mut transport = smtp::SmtpTransportBuilder::localhost()
.expect("Failed to create transport")
.build();
println!("{:?}", transport.send(email.clone()));
}
The EmailBuilder
exposes a fluent API to create an email message. Apart
from the methods shown above, we can set additional headers, CC and reply
addresses, or an HTML body. Finally the build()
method ends the chain
and returns a Result<Email, Error>
. So what happens if we run this example?
$ cargo run
Err(Io(Error { repr: Os { code: 111, message: "Connection refused" } }))
Oops... But notice that we used localhost()
in the transport configuration.
More often than not we won't have an SMTP server installed and running on
our local machine. Let's use GMail as our email backend instead:
use std::env;
let mut transport = smtp::SmtpTransportBuilder::new(("smtp.gmail.com", smtp::SUBMISSION_PORT))
.expect("Failed to create transport")
.credentials(&env::var("GMAIL_USERNAME").unwrap_or("user".to_string())[..],
&env::var("GMAIL_PASSWORD").unwrap_or("password".to_string())[..])
.build();
println!("{:?}", transport.send(email.clone()));
We're reading GMail credentials from environment variables in order not to hardcode them in our program. If all goes well, after running this code we should receive an email!
The SendableEmail
trait
We don't need to use EmailBuilder
every time we want to send an email.
The send()
method of the transport requires its argument to implement
SendableEmail
, nothing else. For example, if we have a custom reporting
system that already has a notion of a report's recipient, we can implement
SendableEmail
for the Report
type and send that instead.
extern crate uuid;
use lettre::email::SendableEmail;
struct Report {
contents: String,
recipient: String,
}
impl SendableEmail for Report {
fn from_address(&self) -> String {
"complicated.report.system@gmail.com".to_string()
}
fn to_addresses(&self) -> Vec<String> {
vec![self.recipient.clone()]
}
fn message(&self) -> String {
format!("\nHere's the report you asked for:\n\n{}", self.contents)
}
fn message_id(&self) -> String {
uuid::Uuid::new_v4().simple().to_string()
}
}
let report = Report {
contents: "Some very important report".to_string(),
recipient: "zbigniew@siciarz.net".to_string(),
};
transport.send(report).expect("Failed to send the report");
Frankly speaking, this trait doesn't feel like it has a complete API yet.
Most notably, there's no way to set the subject. I hope it's just an oversight
and a relevant method will be added to the trait in some future version of
lettre
.
There are a few more missing things in this crate. At the moment, the most important feature that's not implemented is the ability to attach files to emails. However, this is being worked on. Also, a Mailgun backend is already in progress.
Remember that lettre
is still at a 0.x version number, but it's gaining
traction. And hopefully gaining more contributors as well :)