Mailers
A mailer will deliver emails in the background using the existing loco
background worker infrastructure. It will all be seamless for you.
Sending email
To use an existing mailer, mostly in your controller:
use crate::
// in your controllers/auth.rs
async
This will enqueue a mail delivery job. The action is instant because the delivery will be performed later in the background.
Configuration
Configuration for mailers is done in the config/[stage].yaml
file. Here is the default configuration:
# Mailer Configuration.
mailer:
# SMTP mailer configuration.
smtp:
# Enable/Disable smtp mailer.
enable: true
# SMTP server host. e.x localhost, smtp.gmail.com
host:
# SMTP server port
port: 1025
# Use secure connection (SSL/TLS).
secure: false
# auth:
# user:
# password:
Mailer is done by sending emails to a SMTP server. An example configuration for using sendgrid (choosing the SMTP relay option):
# Mailer Configuration.
mailer:
# SMTP mailer configuration.
smtp:
# Enable/Disable smtp mailer.
enable: true
# SMTP server host. e.x localhost, smtp.gmail.com
host:
# SMTP server port
port: 587
# Use secure connection (SSL/TLS).
secure: true
auth:
user: "apikey"
password: "your-sendgrid-api-key"
Default Email Address
Other than specifying email addresses for every email sending task, you can override a default email address per-mailer.
First, override the opts
function in the Mailer
trait, in this example for an AuthMailer
:
Using a mail catcher in development
You can use an app like MailHog
or mailtutan
(written in Rust):
$ cargo install mailtutan
$ mailtutan
listening on smtp://0.0.0.0:1025
listening on http://0.0.0.0:1080
This will bring up a local smtp server and a nice UI on http://localhost:1080
that "catches" and shows emails as they are received.
And then put this in your development.yaml
:
# Mailer Configuration.
mailer:
# SMTP mailer configuration.
smtp:
# Enable/Disable smtp mailer.
enable: true
# SMTP server host. e.x localhost, smtp.gmail.com
host: localhost
# SMTP server port
port: 1025
# Use secure connection (SSL/TLS).
secure: false
Now your mailer workers will send email to the SMTP server at localhost
.
Adding a mailer
You can generate a mailer:
Or, you can define it manually if you like to see how things work. In mailers/auth.rs
, add:
static welcome: = include_dir!;
Each mailer has an opinionated, predefined folder structure:
src/
mailers/
auth/
welcome/ <-- all the parts of an email, all templates
subject.t
html.t
text.t
auth.rs <-- mailer definition
Running a mailer
The mailer operates as a background worker, which means you need to run the worker separately to process the jobs. The default startup command cargo loco start
does not initiate the worker, so you need to run it separately:
To run the worker, use the following command:
To run both the server and the worker simultaneously, use the following command:
Testing
Testing emails sent as part of your workflow can be a complex task, requiring validation of various scenarios such as email verification during user registration and checking user password emails. The primary goal is to streamline the testing process by examining the number of emails sent in the workflow, reviewing email content, and allowing for data snapshots.
In Loco
, we have introduced a stub test email feature. Essentially, emails are not actually sent; instead, we collect information on the number of emails and their contents as part of the testing context.
Configuration
To enable the stub in your tests, add the following field to the configuration under the mailer section in your YAML file:
mailer:
stub: true
Note: If your email sender operates within a worker process, ensure that the worker mode is set to ForegroundBlocking.
Once you have configured the stub, proceed to your unit tests and follow the example below:
Writing a test
Test Description:
- Create an HTTP request to the endpoint responsible for sending emails as part of your code.
- Retrieve the mailer instance from the context and call the deliveries() function, which contains information about the number of sent emails and their content.
use *;
async