While playing with my small Express app a bit (https://dev.to/trannguyenhung011086/first-try-with-express-2a3p), I started to diving more in NodeJS events method to make a simple and manageable email sending scheduler.
Below is how I develop my code from using the native NodeJS eventEmitter to Agenda JS methods.
First of all is the mailer service containing methods:
- send active email when user register
// mailer.js
const config = require('../config');
const mailer = require('@sendgrid/mail');
mailer.setApiKey(config.sendgrid);
module.exports = {
sendActiveEmail: async ({ username, email, url }) => {
try {
const msg = {
to: email,
from: 'learn-svelte@example.com',
subject: 'Your active link',
text: `Hi ${username}, please click the following url to activate your account: ${url}.\nThank you!`,
html: `<p>Hi <strong>${username}</strong>,</p>
<p>Please click <a href=${url}>the following url</a> to activate your account.</p>
<p>Thank you!</p>`,
};
const send = await mailer.send(msg);
console.log('Active email is sent to ' + email);
return send;
} catch (err) {
console.error('Cannot send email!\n', err);
}
},
};
Then come the user service which is used for the Express user routes. Here I define 'register' event for the eventEmitter method.
// userService.js
module.exports = {
...
createUser: async ({ username, email, password }) => {
const payload = { username, email, password };
await validateRegister(payload);
...
payload.password = await bcrypt.hash(password, 10);
let newUser = await UserModel.create(payload);
const send = myEmitter.emit('register', {
username,
email,
url: newUser.activeLink,
});
if (!send) myEmitter.emit('error');
...
return newUser;
},
}
,
Then I call mailer service in event subcribers.
// event.js
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
const mailer = require('../services/mailer');
myEmitter.on('register', async ({ username, email, url }) => {
await mailer.sendActiveEmail({ username, email, url });
});
module.exports = myEmitter;
The above code work quite well for the case of sending active email right after user registers. But what about if I want to send a welcome email in 1 minutes after they click the active link?
First I think of using setTimeout method but it is not an easy way to scale later with more scheduled jobs. That was when I found out about Agenda JS which is a lightweight solution for such purpose.
You can check how to install Agenda as instructed at https://github.com/agenda/agenda. Please note, it requires to connect to Mongo database to work.
Then I started modifying my code a bit as below.
// agenda.js
const Agenda = require('agenda');
const config = require('../config');
const mailer = require('../services/mailer');
const agenda = new Agenda({
db: { address: config.database, collection: 'agendaJobs' },
});
agenda
.on('ready', () => console.log('Agenda started!'))
.on('error', () => console.log('Agenda connection error!'));
agenda.define('register', async job => {
const { username, email, url } = job.attrs.data;
await mailer.sendActiveEmail({ username, email, url });
});
agenda.define('welcome', async job => {
const { username, email } = job.attrs.data;
await mailer.sendWelcomeEmail({ username, email });
});
agenda.start();
module.exports = agenda;
Similar to the native event subscriber method, here I define jobs for Agenda to consume. Then in another file, I define methods for Agenda to publish.
// scheduler.js
const agenda = require('./agenda');
module.exports = {
scheduleActiveEmail: async ({ username, email, url }) => {
await agenda.schedule('in 1 second', 'register', {
username,
email,
url,
});
},
scheduleResendActiveEmail: async ({ username, email, url }) => {
await agenda.schedule('in 1 second', 'resend', {
username,
email,
url,
});
},
scheduleWelcomeEmail: async ({ username, email }) => {
await agenda.schedule('in 30 seconds', 'welcome', { username, email });
},
};
Next, I use the new Agenda publisher methods in user service.
// userService.js
module.exports = {
createUser: async ({ username, email, password }) => {
const payload = { username, email, password };
await validateRegister(payload);
...
payload.password = await bcrypt.hash(password, 10);
let newUser = await UserModel.create(payload);
await scheduler.scheduleActiveEmail({
username,
email,
url: newUser.activeLink,
});
return newUser;
},
activeUser: async ({ userId, uuid }) => {
const user = await getUserById(userId);
...
await scheduler.scheduleWelcomeEmail({
username: user.username,
email: user.email,
});
user.active = true;
user.welcome = true;
await user.save();
return { userId: user._id, active: user.active };
},
};
Finally, after the jobs are processed, I can query of job data stored in AgendaJobs collection on Mongo database.
In summary, Agenda JS is a suitable solution for simple management of scheduling jobs in NodeJS rather than using the native setTimeout or traditional cron jobs.
It even provides a dashboard solution which I will continue to learn more and write about it soon :)
Top comments (3)
Hey, thanks, it's very helpful for me!
Do you have to pass a database URL or can you pass the database connection directly?
Hey, great article! What retry logic do you propose for a job scheduling procedure developed using AgendaJS?