You can send emails from your AdonisJS application using the @adonisjs/mail
package. The mail package is built on top of Nodemailer, bringing the following quality of life improvements over Nodemailer.
- Fluent API to configure mail messages.
- Ability to define emails as classes for better organization and easier testing.
- An extensive suite of officially maintained transports. It includes
smtp
,ses
,mailgun
,sparkpost
,resend
, andbrevo
. - Improved testing experience using the Fakes API.
- Mail messenger to queue emails.
- Functional APIs to generate calendar events.
Installation
Install and configure the package using the following command :
node ace add @adonisjs/mail
# Pre-define transports to use via CLI flag
node ace add @adonisjs/mail --transports=resend --transports=smtp
-
Installs the
@adonisjs/mail
package using the detected package manager. -
Registers the following service provider and command inside the
adonisrc.ts
file.{commands: [// ...other commands() => import('@adonisjs/mail/commands')],providers: [// ...other providers() => import('@adonisjs/mail/mail_provider')]} -
Create the
config/mail.ts
file. -
Defines the environment variables and their validations for the selected mail services
Configuration
The configuration for the mail package is stored inside the config/mail.ts
file. Inside this file, you may configure multiple email services as mailers
to use them within your application.
See also: Config stub
import env from '#start/env'
import { defineConfig, transports } from '@adonisjs/mail'
const mailConfig = defineConfig({
default: 'smtp',
/**
* A static address for the "from" property. It will be
* used unless an explicit from address is set on the
* Email
*/
from: {
address: '',
name: '',
},
/**
* A static address for the "reply-to" property. It will be
* used unless an explicit replyTo address is set on the
* Email
*/
replyTo: {
address: '',
name: '',
},
/**
* The mailers object can be used to configure multiple mailers
* each using a different transport or the same transport with a different
* options.
*/
mailers: {
smtp: transports.smtp({
host: env.get('SMTP_HOST'),
port: env.get('SMTP_PORT'),
}),
resend: transports.resend({
key: env.get('RESEND_API_KEY'),
baseUrl: 'https://api.resend.com',
}),
},
})
-
default
-
The name of the mailer to use by default for sending emails.
-
from
-
A static global address to use for the
from
property. The global address will be used unless an explicitfrom
address is defined on the email. -
replyTo
-
A static global address to use for the
reply-to
property. The global address will be used unless an explicitreplyTo
address is defined on the email. -
mailers
-
The
mailers
object is used to configure one or more mailers you want to use for sending emails. You can switch between the mailers at runtime using themail.use
method.
Transports config
Following is a complete reference of configuration options accepted by the officially supported transports.
See also: TypeScript types for config object
The following configuration options are sent to the Mailgun's /messages.mime
API endpoint.
{
mailers: {
mailgun: transports.mailgun({
baseUrl: 'https://api.mailgun.net/v3',
key: env.get('MAILGUN_API_KEY'),
domain: env.get('MAILGUN_DOMAIN'),
/**
* The following options can be overridden at
* runtime when calling the `mail.send` method.
*/
oDkim: true,
oTags: ['transactional', 'adonisjs_app'],
oDeliverytime: new Date(2024, 8, 18),
oTestMode: false,
oTracking: false,
oTrackingClick: false,
oTrackingOpens: false,
headers: {
// h:prefixed headers
},
variables: {
appId: '',
userId: '',
// v:prefixed variables
}
})
}
}
The following configuration options are forwarded to Nodemailer as it is. So please check the Nodemailer documentation as well.
{
mailers: {
smtp: transports.smtp({
host: env.get('SMTP_HOST'),
port: env.get('SMTP_PORT'),
secure: false,
auth: {
type: 'login',
user: env.get('SMTP_USERNAME'),
pass: env.get('SMTP_PASSWORD')
},
tls: {},
ignoreTLS: false,
requireTLS: false,
pool: false,
maxConnections: 5,
maxMessages: 100,
})
}
}
The following configuration options are forwarded to Nodemailer as it is. So please check the Nodemailer documentation as well.
Make sure to install the @aws-sdk/client-ses
package to use the SES transport.
{
mailers: {
ses: transports.ses({
/**
* Forwarded to aws sdk
*/
apiVersion: '2010-12-01',
region: 'us-east-1',
credentials: {
accessKeyId: env.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: env.get('AWS_SECRET_ACCESS_KEY'),
},
/**
* Nodemailer specific
*/
sendingRate: 10,
maxConnections: 5,
})
}
}
The following configuration options are sent to the SparkPost's /transmissions
API endpoint.
{
mailers: {
sparkpost: transports.sparkpost({
baseUrl: 'https://api.sparkpost.com/api/v1',
key: env.get('SPARKPOST_API_KEY'),
/**
* The following options can be overridden at
* runtime when calling the `mail.send` method.
*/
startTime: new Date(),
openTracking: false,
clickTracking: false,
initialOpen: false,
transactional: true,
sandbox: false,
skipSuppression: false,
ipPool: '',
})
}
}
The following configuration options are sent to the Resend's /emails
API endpoint.
{
mailers: {
resend: transports.resend({
baseUrl: 'https://api.resend.com',
key: env.get('RESEND_API_KEY'),
/**
* The following options can be overridden at
* runtime when calling the `mail.send` method.
*/
tags: [
{
name: 'category',
value: 'confirm_email'
}
]
})
}
}
Basic example
Once the initial configuration is completed, you may send emails using the mail.send
method. The mail service is a singleton instance of the MailManager class created using the config file.
The mail.send
method passes an instance of the Message class to the callback and delivers the email using the default
mailer configured inside the config file.
In the following example, we trigger an email from the controller after creating a new user account.
import User from '#models/user'
import { HttpContext } from '@adonisjs/core/http'
import mail from '@adonisjs/mail/services/main'
export default class UsersController {
async store({ request }: HttpContext) {
/**
* For demonstration only. You should validate the data
* before storing it inside the database.
*/
const user = await User.create(request.all())
await mail.send((message) => {
message
.to(user.email)
.from('info@example.org')
.subject('Verify your email address')
.htmlView('emails/verify_email', { user })
})
}
}
Queueing emails
Since sending emails can be time-consuming, you might want to push them to a queue and send emails in the background. You can do the same using the mail.sendLater
method.
The sendLater
method accepts the same parameters as the send
method. However, instead of sending the email immediately, it will use the Mail messenger to queue it.
await mail.send((message) => {
await mail.sendLater((message) => {
message
.to(user.email)
.from('info@example.org')
.subject('Verify your email address')
.htmlView('emails/verify_email', { user })
})
By default, the mail messenger uses an in-memory queue, meaning the queue will drop the jobs if your process dies with pending jobs. This might not be a huge deal if your application UI allows re-sending emails with manual actions. However, you can always configure a custom messenger and use a database-backed queue.
Using bullmq for queueing emails
npm i bullmq
In the following example, we use the mail.setMessenger
method to configure a custom queue that uses bullmq
under the hood for storing jobs.
We store the compiled email, runtime configuration, and the mailer name inside the job. Later, we will use this data to send emails inside a worker process.
import { Queue } from 'bullmq'
import mail from '@adonisjs/mail/services/main'
const emailsQueue = new Queue('emails')
mail.setMessenger((mailer) => {
return {
async queue(mailMessage, config) {
await emailsQueue.add('send_email', {
mailMessage,
config,
mailerName: mailer.name,
})
}
}
})
Finally, let's write the code for the queue Worker. Depending on your application workflow, you may have to start another process for the workers to process the jobs.
In the following example:
- We process jobs named
send_email
from theemails
queue. - Access compiled mail message, runtime config, and the mailer name from the job data.
- And send the email using the
mailer.sendCompiled
method.
import { Worker } from 'bullmq'
import mail from '@adonisjs/mail/services/main'
new Worker('emails', async (job) => {
if (job.name === 'send_email') {
const {
mailMessage,
config,
mailerName
} = job.data
await mail
.use(mailerName)
.sendCompiled(mailMessage, config)
}
})
That's all! You may continue using the mail.sendLater
method. However, the emails will be queued inside a redis database this time.
Switching between mailers
You may switch between the configured mailers using the mail.use
method. The mail.use
method accepts the name of the mailer (as defined inside the config file) and returns an instance of the Mailer class.
import mail from '@adonisjs/mail/services/main'
mail.use() // Instance of default mailer
mail.use('mailgun') // Mailgun mailer instance
You may call the mailer.send
or mailer.sendLater
methods to send email using a mailer instance. For example:
await mail
.use('mailgun')
.send((message) => {
})
await mail
.use('mailgun')
.sendLater((message) => {
})
The mailer instances are cached for the lifecycle of the process. You may use the mail.close
method to destroy an existing instance and re-create a new instance from scratch.
import mail from '@adonisjs/mail/services/main'
/**
* Close transport and remove instance from
* cache
*/
await mail.close('mailgun')
/**
* Create a fresh instance
*/
mail.use('mailgun')
Configuring the template engine
By default, the mail package is configured to use the Edge template engine for defining the email HTML and Plain text contents.
However, as shown in the following example, you may also register a custom template engine by overriding the Message.templateEngine
property.
See also: Defining email contents
import { Message } from '@adonisjs/mail'
Message.templateEngine = {
async render(templatePath, data) {
return someTemplateEngine.render(templatePath, data)
}
}
Events
Please check the events reference guide to view the list of events dispatched by the @adonisjs/mail
package.