Mail.raw does not do anything inside an event listener


#1

I am using the Persona package and utilizing the events that it emits. I have written a listener to listen for when the user::created event is emitted and send a confirmation email. However, inside of my User listener, the method does nothing.

I’ve attempted to place the Mail.raw call in a try/catch to see if there were any unhandled errors but to my dismay, the catch block is never reached. The same can be said for any code expected to be executed after Mail.raw. When using a fake instance to pull the recent events, I do see that they’re being emitted correctly, however, the Mail fake instance simply returns undefined.

Here is my code:

// app/Listeners/User.js

'use strict'
const Mail = use('Mail')

const User = exports = module.exports = {}

User.sendEmailConfirmation = async ({ user, token }) => {
  await Mail.raw(`Your email verification code is: ${token}`, message => {
    message
      .from('FooBar <no-reply@example.com>')
      .to(user.email)
      .subject('Confirm your FooBar account')
  })
}
// test/functional/user.spec.js

'use strict'

const { test, trait } = use('Test/Suite')('User')
const Mail = use('Mail')
const Event = use('Event')

trait('Test/ApiClient')

test('create user', async ({ client }) => {
  Mail.fake()
  Event.fake()

  const data = {
    username: 'foo',
    email: 'foo@example.com',
    password: 'bar',
    password_confirmation: 'bar'
  }

  const response = await client
    .post('v1/users')
    .send(data)
    .end()

  const recentEmail = Mail.recent()
  console.log(recentEmail)

  response.assertStatus(201)
  Mail.restore()
  Event.restore()
})

In addition, I have also confirmed that Mail.raw works inside of the controller method itself.

Thank you,
Sean.


#2

When you fake the Event, the original event handler doesn’t execute, meaning no call to the Mail has been made


#3

Hello Virk,

Appreciate the reply. I didn’t remember to mention that I had tried without faking Event or Mail initially and found that the same thing happened as described in my original post.

Thank you,
Sean.


#4

Can you share the final code that you expect to work but isn’t working?

Also include the controller code too


#5

Here you go:

It starts in app/Controllers/Http/UserController.js when the store method is called:

// app/Controllers/Http/UserController.js

'use strict'
const Persona = use('Persona')

class UserController {

  // ...

  async store ({ request, response }) {
    const payload = request.only([
      'username',
      'email',
      'password',
      'password_confirmation'
    ])

    await Persona.register(payload)

    response.created()
  }

  // ...

}

module.exports = UserController

Persona emits a user::created event where I have defined the according listener:

// start/events.js

const Event = use('Event')

Event.on('user::created', 'User.sendEmailConfirmation')

Inside the listener is where my Mail code is located and malfunctioning:

// app/Listeners/User.js

'use strict'
const Mail = use('Mail')

const User = exports = module.exports = {}

User.sendEmailConfirmation = async ({ user, token }) => {
  await Mail.raw(`Your email verification code is: ${token}`, message => {
    message
      .from('Example <no-reply@example.com>')
      .to(user.email)
      .subject('Confirm your Example account')
  })
}

Thanks,
Sean.


#6

Hmm, some interesting behavior here.

I tested it without using vow and the emails are being sent now. Perhaps there is a bug?


#7

This is what the flow is of your program.

The action taken on an event doesn’t happen in sequence and when you run your test assertion, the event listener is getting executed in background and that’s the whole point of using events, since you do not want to make the request wait for the email to be sent.


#8

Hello Virk,

That makes sense. I have since revised the code to trap the event just to ensure that it is being fired with the correct data. Further issues can be assumed as mail configuration issues.

I appreciate the help Virk!

Thank you,
Sean.

P.S. What did you use to make that flowchart?


#9

Sure, using https://mermaidjs.github.io


#10

Just a note here… it is possible to verify an email is sent, one must include a delay in their test to give the event time to actually complete.

I’ve found 50ms is plenty of time for my test of generation of the welcome email via persona – actually 25ms works most of the time, but I had to up it when I ran the test in a vm that was running more slowly than my native machine.

I have a helper function sleep for this purpose:

  async sleep (ms) {
    return new Promise(resolve => {
      setTimeout(resolve, ms)
    })
  }

My test that a welcome email is generated on user registration with persona looks like this:

test('user creation generates welcome email', async ({ assert, client }) => {
  Mail.fake()
  const response = await client
    .post('/api/v1/users')
    .send({
      email: 'test3@fake.io',
      first_name: 'Test',
      last_name: 'UserThree',
      password: 'test',
    })
    .loginVia(this.user, 'jwt')
    .end()

  response.assertStatus(200)
  assert.equal(response.body.status, 'success')
  assert.equal(response.body.message, 'User created')
  assert.equal(response.body.data.id, 3)

  await sleep(50)

  const recentEmail = Mail.pullRecent()

  assert.exists(recentEmail.envelope)
  assert.exists(recentEmail.message)
  assert.include(recentEmail.envelope.to, 'test3@fake.io')

  Mail.restore()
})

Works great.