AdonisJs - Events and Mailing Part 1
Tags: adonisjs, javascript, node, beginners
Published: 2021-09-24 20:21:11

Welcome back to the fourth installment of the AdonisJs series!

A web framework's ability to handle events and mailing greatly add to its appeal. Luckily, Adonis provides these in an easily configurable way that will set you on your path to building robust APIs and web apps. 

In this article, I'll show just how we go about these two crucial concepts in modern software development. In order to achieve our goal, we'll build upon the [previous article](https://dev.to/tngeene/adonisjs-understanding-user-registration-and-authentication-2ojl), where we talked about user registration and login.

If you're not familiar with the concept of events in software development, worry not.

Events can be described as actions that are triggered by another action in a system, for example, you'd want your users to be emailed upon successful registration. In this case, the email sending is an event triggered by successful user registration.

## What we'll be working on

For this tutorial, we're going to implement an event that emails our users an activation link for their accounts. The purpose of this is to enforce security in our application.
We definitely wouldn't have bots and fictitious accounts registering in our application.

We'll look at two approaches we can employ to achieve this, but before we do that, let's set up the mailer.

## Installing Adonis Mailing package.
Since we'll need users to receive emails, we need a way to make our system be able to send them. Luckily, Adonis also has a [mailer package](https://docs.adonisjs.com/guides/mailer#document) for this. To install, run 

     npm i @adonisjs/mail

As usual, we also need to configure the package preferences,

   node ace invoke @adonisjs/mail

For my configuration, I'll be using SMTP

![tngeene adonis mail config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5v264scsgu65s07g0kb.png)

Open up the `env.ts` file and paste the following values.

    SMTP_HOST: Env.schema.string({ format: 'host' }),
    SMTP_PORT: Env.schema.number(),
    SMTP_USERNAME: Env.schema.string(),
    SMTP_PASSWORD: Env.schema.string(),
    DEFAULT_FROM_EMAIL: Env.schema.string(),

These values are the environment variables for our SMTP configuration. I recommend validating them in the `env.ts` file. 
The `DEFAULT_FROM_EMAIL` is the email that will appear as the sender from our application.
The main environment variables will reside in the `.env` file. We'll get there in a minute.

### Setting up our SMTP service

The Simple Mail Transfer Protocol(SMTP)  is an internet standard communication protocol for electronic mail transmission. Mail servers and other message transfer agents use SMTP to send and receive mail messages. 

There are a number of SMTP providers; including, google, mailgun, SendGrid...and so on. However, since we are not building a production-ready application yet, I'll be using [mailtrap.](https://mailtrap.io/) 

![tngeene adonis mailtrap](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfwf9dis84zzovmg66yd.png)

Mailtrap is an email testing tool that Captures SMTP traffic from staging and dev environments. Simply put, it mocks real-world emails in a sandbox environment. Head over to their [website](https://mailtrap.io/), create an account, and navigate to the demo inbox section.

![tngeene adonisjs mailtrap demo inbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7sgdqr8onrwc92rr3hgl.png)

Next, open the .env file located in your project's root directory and paste these constants.

      SMTP_HOST=smtp.mailtrap.io
      SMTP_PORT=2525
      SMTP_USERNAME=<your_mailtrap_username>
      SMTP_PASSWORD=<your_mailtrap_password>
      DEFAULT_FROM_EMAIL=admin@fitit.com #varies

The username and password values are found on the mailtrap dashboard, under the `integrations` section.

Now that we have all that setup, we'll get straight right into actual coding. 

## Approach 1 - Using a function in a model
This approach will involve us having a function inside our user model that will handle email sending. We'll call this function in our authController, right after a successful registration.

Open the `app/Models/User.ts`. We'll import several packages at the top,

```typescript
import Mail from '@ioc:Adonis/Addons/Mail'
import Env from '@ioc:Adonis/Core/Env'
import Route from '@ioc:Adonis/Core/Route'
```
Next, after all the model column definitions, we'll write a `sendVerificationEmail` function.

```typescript

  public async sendVerificationEmail() {
    const appDomain = Env.get('APP_URL')
    const appName = Env.get('APP_NAME')
    const currentYear = new Date().getFullYear()
    const url = Route.builder()
      .params({ email: this.email })
      .prefixUrl(appDomain)
      .makeSigned('verifyEmail', { expiresIn: '24hours' })
    Mail.send((message) => {
      message
        .from(Env.get('DEFAULT_FROM_EMAIL')
        .to(this.email)
        .subject('Please verify your email')
        .htmlView('emails/auth/verify', { user: this, url, appName, appDomain, currentYear })
    })
  }
```

The code above is contains several building blocks that helps to achieve the email sending functionality.

```typescript

    const appDomain = Env.get('APP_URL')
    const appName = Env.get('APP_NAME')
```

These are values which reside in our `.env` file. If you don't have them already, you can check out the `env.example` file of this [project repo](https://github.com/tngeene/adonis-tutorial-fitit)

My current values are 

      APP_NAME=Fitit
      APP_URL=http://127.0.0.1:3333

```typescript
 const currentYear = new Date().getFullYear()
```
 we get the current year. We'll inject this as a variable in our html template for our email

```typescript
const url = Route.builder()
      .params({ email: this.email })
      .prefixUrl(appDomain)
      .makeSigned('verifyEmail', { expiresIn: '24hours' })
```

We're using the Route package to encode our user data into an activation token. The `makeSigned()` function is tied to a `verifyEmail` route that we'll build in the next part. Finally, we set an expiry period for the token. For this case, I set the period to 24 hours. That way, if a user fails to verify their account within that duration, their account won't be activated.

```typescript
Mail.send((message) => {
      message
        .from(Env.get('DEFAULT_FROM_EMAIL')
        .to(this.email)
        .subject('Please verify your email')
        .htmlView('emails/auth/verify', { user: this, url, appName, appDomain, currentYear })
    })

```
This section makes use of the adonis mailer package. We're giving instructions to the program to send an email to the created user's email. The email will appear as received from the value `DEFAULT_FROM_EMAIL` in our `.env` file.
The subject line will be, "Please verify your email".

The `htmlView` method contains the template our email will read from, that is, the html code that styles our verification email.
For this, create a folder named `resources` in the project root directory. The entry point for all Adonis html templates must be located within this folder. 
Since we initialized this project as an API only, we'll install the package that enables us to have `.edge` templates

   npm i @adonisjs/view

The first argument the function takes is the html template to read from.
Create a `emails\auth\verify.edge` file and paste the code from this [github gist](https://gist.github.com/tngeene/78a312be1151c46a994b21c1729cf3d5). The next argument it takes are the variables to be read in the html template. In our case, 
     
    { user: this, url, appName, appDomain, currentYear }

### Writing the Verify Email controller
We're going to write an email verification controller that will be responsible for validating and activating a user's account.

    node ace make:controller users/EmailVerificationsController

Open the created file and copy this snippet.

```typescript
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
import { DateTime } from 'luxon'

export default class EmailVerificationsController {
  public async confirm({ response, request, params }: HttpContextContract) {
    if (request.hasValidSignature()) {
      const user = await User.findByOrFail('email', params.email)
      if (!user.isActivated) {
        user.email_verified_at = DateTime.local()
        user.isActivated = true
        user.save()
        return response.status(202).send({ message: 'Account verified and activated' })
      } else {
        return response.status(409).send({ message: 'Account was already verified' })
      }
    } else {
      return response.status(403).send({ error: { message: 'Invalid token' } })
    }
  }
}
```

Next, copy this into the users.ts file under the routes directory

```typescript
Route.get('/verify-email/:email', 'users/EmailVerificationsController.confirm').as('verifyEmail')
```

We're mapping the controller to a route. In adonis, routes can be given custom names, by defining the named route to a function, `as()`. In our case, the route is called `verifyEmail`. If you're keen enough, you'll notice that we passed this named route as a parameter in the model function we defined in the User model.

```typescript
    .makeSigned('verifyEmail', { expiresIn: '24hours' })
    })
```

From the email verification controller code, we can see we have the `confirm()` function.
This block contains the user activation logic, that is, immediately the link is hit, we verify the user and activate his account. It also contains constraints that check whether the URL pattern matches the valid the signature, hence the `makeSigned()` and `hasValidSignatureSections()`


Finally, we modify the AuthController to send the email after a successful registration. Right after we save our user in the database,

```typescript
const user = await User.create(data)
    // send verification email
    user?.sendVerificationEmail()

    return response
      .status(201)
      .send({ success: 'Registration successful, check your email inbox for a verification email' })
```

### Testing 

For testing open up your postman and repeat the registration steps we used in the [previous article](https://dev.to/tngeene/adonisjs-understanding-user-registration-and-authentication-2ojl). Create a new user, and head over to your mailtrap demo inbox.

If everything worked well, then the following images should be what you see.

![tngeene events adonis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ognw8ikao4dvag1x3ax8.png)
![tngeene events adonis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0c92k8qrwisc6ckmo4g1.png)
![tngeene events adonis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uo5wzhaa4jrd95jcsi5s.png)

There's a lot of content to cover in events. The method covered in this piece is just one way to go about it. To keep the article short, I've decided to cover the next approach in the next piece. 

If you have any comments, queries, don't hesitate to leave a comment or email. 
Until then, stay Adonis. It is the way!