Node.js  

Scheduling Cron Jobs & Sending Email Notifications in MERN Stack

Introduction

In real-world MERN stack applications, background jobs like sending email reminders, cleaning up records, or generating reports are common tasks. These are best handled outside the request/response cycle, using scheduled cron jobs.

These automated background tasks improve user experience and system efficiency. For example, an app that reminds users about pending tasks or upcoming deadlines can automatically send emails at specific times daily without requiring user action or manual triggers.

This article walks you through.

  • Setting up cron jobs in a Node.js backend using the node-cron library.
  • Sending email reminders with Nodemailer.
  • Triggering cron jobs to email users daily/weekly (e.g., for reminders or notifications).

Project Setup

For this demo, we focus on the backend (Express.js). The stack,

  • MongoDB: Stores users and their notification preferences.
  • Express.js: Defines API routes and manages HTTP requests.
  • Nodemailer: Sends transactional or reminder emails.
  • Node-cron: Runs background jobs on a defined schedule (like every day at 8 AM).

This backend-centric approach ensures the cron jobs run independently of the frontend, which is essential for tasks that need to run regardless of user activity.

Project Structure

backend/
├── config/
│   └── mailConfig.js
├── cron/
│   └── reminderJob.js
├── controllers/
│   └── userController.js
├── models/
│   └── User.js
├── routes/
│   └── userRoutes.js
├── .env
├── server.js

This modular file structure separates concerns clearly: configuration, scheduling logic, database models, and route handling are all decoupled and easier to manage as your app grows.

1. Install Dependencies

We need core packages for email, scheduling, environment config, and MongoDB. Here's what each one does.

  • express: Builds the RESTful API server
  • mongoose: Connects to and interacts with MongoDB
  • dotenv: Loads sensitive credentials like email and DB config from .env
  • node-cron: Schedules periodic background tasks
  • nodemailer: Sends emails through SMTP or other mail services

These packages together enable automation, scheduling, and communication, which is vital in modern web applications.

npm init -y
npm install express mongoose dotenv node-cron nodemailer

2. MongoDB User Model

We create a user model where each user has.

  • email: Address to send reminders to.
  • receiveReminders: A boolean flag to enable/disable notifications.

This basic structure allows flexibility. In production, you might also store user preferences like time zones, frequency (daily/weekly), or opt-in consents.

models/User.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  email: { type: String, required: true },
  receiveReminders: { type: Boolean, default: true },
});

module.exports = mongoose.model('User', userSchema);

3. Configure Nodemailer

Nodemailer allows your backend to send emails. We use Gmail's SMTP here for simplicity, but in production, you'd prefer.

  • Mailgun
  • SendGrid
  • Amazon SES

Storing credentials in .env protects sensitive data from source control exposure and makes it easy to switch between environments.

config/mailConfig.js

const nodemailer = require('nodemailer');
require('dotenv').config();

const transporter = nodemailer.createTransport({
  service: 'gmail',
  auth: {
    user: process.env.MAIL_USER,
    pass: process.env.MAIL_PASS,
  },
});

module.exports = transporter;

Add .env

[email protected]
MAIL_PASS=yourpassword

4. Schedule a Cron Job

Cron jobs run scheduled tasks using a time-based syntax. Common examples.

  • Every minute: * * * * *
  • Every hour: 0 * * * *
  • Daily at 8 AM: 0 8 * * *

This setup is useful for things like.

  • Daily reminder emails
  • Weekly digests
  • Monthly billing summaries

node-cron integrates seamlessly with Express and gives us full control over scheduling and frequency.

cron/reminderJob.js

const cron = require('node-cron');
const User = require('../models/User');
const transporter = require('../config/mailConfig');

function sendReminderEmail(email) {
  const mailOptions = {
    from: process.env.MAIL_USER,
    to: email,
    subject: '⏰ Daily Reminder',
    text: 'This is your daily reminder from our app!',
  };

  transporter.sendMail(mailOptions, (err, info) => {
    if (err) console.error(`Error sending to ${email}:`, err);
    else console.log(`Reminder sent to ${email}`);
  });
}

const startReminderJob = () => {
  // Every day at 8 AM
  cron.schedule('0 8 * * *', async () => {
    console.log('Running daily reminder job');

    const users = await User.find({ receiveReminders: true });
    users.forEach(user => {
      sendReminderEmail(user.email);
    });
  });
};

module.exports = startReminderJob;

5. Set up Express Server and Initialize Job

We start the reminder job after MongoDB connects, ensuring our app doesn't crash due to uninitialized models. This pattern is essential in real-world apps where multiple services depend on one another.

server.js

const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
const userRoutes = require('./routes/userRoutes');
const startReminderJob = require('./cron/reminderJob');

dotenv.config();
const app = express();
app.use(express.json());

mongoose.connect(process.env.MONGO_URI)
  .then(() => {
    console.log('MongoDB connected');
    startReminderJob(); // Start the job after DB connects
  })
  .catch(err => console.error(err));

app.use('/api/users', userRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

6. API to Register Users

We provide a RESTful API endpoint to register users who want to receive email reminders. You can later add user authentication or preferences for time/frequency.

routes/userRoutes.js

const express = require('express');
const router = express.Router();
const User = require('../models/User');

// Register user
router.post('/register', async (req, res) => {
  try {
    const { email } = req.body;
    const user = new User({ email });
    await user.save();
    res.status(201).json({ message: 'User registered!' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

module.exports = router;

7. Testing Your Setup

Run your server

node server.js

Use Postman to POST to /api/users/register.

{
  "email": "[email protected]"
}

If your time matches the cron pattern (8 AM), the job will run, and an email will be sent to the registered users with receiveReminders: true.

Best Practices

Area Tip
Email Security Use OAuth or App Passwords for Gmail. Never store real credentials in code.
Cron Management For complex jobs, consider libraries like Agenda or Bull.
Rate Limiting Prevent spam by limiting how often reminders go out.
Opt-In/Opt-Out Always allow users to unsubscribe or modify preferences.
Monitoring Log successes/failures to a file or monitoring tool like Sentry.

Conclusion

By combining node-cron and nodemailer, you can automate crucial backend tasks such as daily email reminders, report generation, or system cleanup. This pattern improves reliability, reduces manual workload, and enhances the overall UX of your MERN applications.

Whether you're building a productivity app, a subscription service, or a notification system, adding scheduled background jobs is a powerful upgrade to your backend infrastructure.