Sending emails
A useful feature in many contexts.
When a user creates an account, we could send an email to him confirming that his account has been successfully created.
This solution was inspired by this article from Marc Stammerjohann (blog).
First, we should install handlebars. It is a template engine, capable of generating an HTML template dynamically. It allows for holding the email structure while also receiving data.
yarn add hbs
After that, install the dependencies required for email handling.
yarn add @nestjs-modules/mailer nodemailer
yarn add -D @types/nodemailer
We should then create an account with an email provider, so that we can send emails through the system by using its credentials. Here, we'll use MailTrap due to its simple setup. Create a free account and go to the Email Testing section, in order to obtain the credentials. Due to its testing nature, we won't be sending an actual email, but we'll be able to see if it succeeded on the dashboard. That is, if an actual email would be sent in a real case.
Now, create a module and a service for mailing. Also export the service.
nest g mo mail
nest g s mail
In the same directory, create the folder templates, for holding the handlebars templates.
Then, define the environment variables for the email server, Use your actual credentials here, but set this sample data in the example file.
MAIL_PROTOCOL = smtp
MAIL_HOST = smtp.example.com
MAIL_USER = username
MAIL_PASSWORD = pass123
MAIL_FROM = noreply@mail.com
Also update the ENV_VALIDATION_SCHEMA
, and break a line for better readability. Note the new validation rules.
MAIL_PROTOCOL: Joi.valid('smtp', 'smtps').required(),
MAIL_HOST: Joi.string().hostname().required(),
MAIL_USER: Joi.required(),
MAIL_PASSWORD: Joi.required(),
MAIL_FROM: Joi.string().email().required(),
As we are used to, the next step is to create a configuration namespace. We may do so in mail/config/mail.config. We use the credentials to create the transport
url, very similarly to the database configuration. Also, a default is used for the from
email, and the template engine is also configured.
export default registerAs('mail', () => {
const protocol = process.env.MAIL_PROTOCOL;
const host = process.env.MAIL_HOST;
const user = process.env.MAIL_USER;
const password = process.env.MAIL_PASSWORD;
const from = process.env.MAIL_FROM;
const transport = `${protocol}://${user}:${password}@${host}`;
const config = {
transport,
defaults: {
from: `No Reply <${from}>`,
},
template: {
dir: resolve(__dirname, '..', 'templates'),
adapter: new HandlebarsAdapter(),
options: { strict: true },
},
} as const satisfies MailerOptions;
return config;
});
In the MailModule
, import the MailerModule
, passing this configuration.
MailerModule.forRootAsync(mailConfig.asProvider())
Then, inside the templates folder, we may create the template account-confirmation.hbs.
<p>Hello {{name}},</p>
<p>Your account has been successfully created!</p>
<p><strong>The Conrod Shop</strong></p>
However, the templates are not included in the build by default. Due to this, let's go to the nest-cli.json file and add the following inside the compilerOptions
. We also enable watch mode for them.
"assets": ["**/*.hbs"],
"watchAssets": true
In the MailService
, we may now inject the MailerService
. Finally, let's create a method for sending an "account created" confirmation email to the user. We also pass the context
with the data for the template. And return the result just in case we may want to inspect it.
sendAccountConfirmation(user: User) {
const { name, email } = user;
const mailData = {
to: email,
subject: 'Conrod Shop - Account created',
template: './account-confirmation',
context: { name },
} as const satisfies ISendMailOptions;
return this.mailerService.sendMail(mailData);
}
Now, we should import the MailModule
in the UsersModule
, and inject the MailService
in the UsersSubscriber
. It is here that we'll send the email after the account is created, in order to prevent bloating the UsersService
. Lastly, to only send this email after the user is successfully created, this will be done in the afterInsert()
listener.
async afterInsert(event: InsertEvent<User>) {
const { entity: user } = event;
await this.mailService.sendAccountConfirmation(user);
}
Commit - Sending email when user is created
Last updated