ostrio:mailer

v2.1.4Published 3 years ago

This package has not had recent updates. Please investigate it's current state before committing to using it in your project.

Meteor Mailer (MailTime)

This is port of mail-time NPM library.

Micro-service package for mail queue, with Server and Client API. Build on top of nodemailer package.

Every MailTime instance can be configured to be a Server or Client.

Main difference of Server from Client - Server handles queue and actually sends email. While Client is only put emails into the queue.

ToC

Main features:

  • 👷‍♂️ ~85% tests coverage
  • 📦 Two simple dependencies, written from scratch for top performance
  • 😎 Synchronize email queue across multiple servers
  • 💪 Bulletproof design, built-in retries

How it works?:

Single point of failure

Issue - classic solution with single point of failure:

|----------------|         |------|         |------------------|
|  Other mailer  | ------> | SMTP | ------> |  ^_^ Happy user  |
|----------------|         |------|         |------------------|

The cheme above will work as long as SMTP service is available
or connection between your server and SMPT is up. Once network
failure occurs or SMTP service is down - users won't be happy

|----------------|  \ /    |------|         |------------------|
|  Other mailer  | --X---> | SMTP | ------> | 0_o Disappointed |
|----------------|  / \    |------|         |------------------|
                     ^- email lost in vain

Single SMTP solution may work in case of network or other failures
As long as MailTime has not received confirmation what email is sent
it will keep the letter in the queue and retry to send it again

|----------------|    /    |------|         |------------------|
|   Mail Time    | --X---> | SMTP | ------> |  ^_^ Happy user  |
|---^------------|  /      |------|         |------^-----------|
     \-------------/ ^- We will try later         /
      \- put it back into queue                  /
       \----------Once connection is back ------/

Multiple SMTP providers

Backup scheme with multiple SMTP providers

                           |--------|
                     /--X--| SMTP 1 |
                    /   ^  |--------|
                   /    \--- Retry with next provider
|----------------|/        |--------|         |------------------|
|   Mail Time    | ---X--> | SMTP 2 |      /->|  ^_^ Happy user  |
|----------------|\   ^    |--------|     /   |------------------|
                   \  \--- Retry         /
                    \      |--------|   /
                     \---->| SMTP 3 |--/
                           |--------|

Cluster issue

Let's say you have an app which is growing fast. At some point, you've decided to create a "Cluster" of servers to balance the load and add durability layer.

Also, your application has scheduled emails, for example, once a day with recent news. While you have had single server emails was sent by some daily interval. So, after you made a "Cluster" of servers - each server has its own timer and going to send a daily email to our user. In such case - users will receive 3 emails, sounds not okay.

Here is how we solve this issue:

|===================THE=CLUSTER===================| |=QUEUE=| |===Mail=Time===|
| |----------|     |----------|     |----------|  | |       | |=Micro-service=|   |--------|
| |   App    |     |   App    |     |   App    |  | |       | |               |-->| SMTP 1 |------\
| | Server 1 |     | Server 2 |     | Server 3 |  | |    <--------            |   |--------|       \
| |-----\----|     |----\-----|     |----\-----|  | |    -------->            |                |-------------|
|        \---------------\----------------\---------->      | |               |   |--------|   |     ^_^     |
| Each of the "App Server" or "Cluster Node"      | |       | |               |-->| SMTP 2 |-->| Happy users |
| runs Mail Time as a Client which only puts      | |       | |               |   |--------|   |-------------|
| emails into the queue. Aside to "App Servers"   | |       | |               |                    /
| We suggest running Mail Time as a Micro-service | |       | |               |   |--------|      /
| which will be responsible for making sure queue | |       | |               |-->| SMTP 3 |-----/
| has no duplicates and to actually send emails   | |       | |               |   |--------|
|=================================================| |=======| |===============|

Features

  • Queue - Managed via MongoDB, and will survive server reboots and failures
  • Support for multiple server setups - "Cluster", Phusion Passenger instances, Load Balanced solutions, etc.
  • Emails concatenation by addressee email - Reduce amount of sent email to single user with concatenation, and avoid mistakenly doubled emails
  • When concatenation is enabled - Same emails wouldn't be sent twice, if for any reason, due to bad logic or application failure emails is sent twice or more times - here is solution to solve this annoying behavior
  • Balancing for multiple nodemailer's transports, two modes - backup and balancing. Most useful feature - allows to reduce the cost of SMTP services and add durability. So, if any of used transports are failing to send an email it will switch to next one
  • Sending retries for network and other failures
  • Template support with Mustache-like placeholders

Prerequisites

If you're working on Server functionality - first you will need nodemailer, although this package is meant to be used with nodemailer, it's not added as the dependency, as it not needed by Client, and you're free to choose nodemailer's version to fit your needs:

meteor npm install --save nodemailer

Installation & Import (via Atmosphere):

Install MailTime package:

meteor add ostrio:mailer

ES6 Import:

1import { MailTime } from 'meteor/ostrio:mailer';

Installation & Import (via NPM):

Install MailTime package:

meteor npm install --save mail-time

ES6 Import:

1import MailTime from 'mail-time';

Please see NPM version docs at mail-time project page

Usage:

To create nodemailer's transports see nodemailer docs and mail-time project page

1import nodemailer from 'nodemailer';
2const transports = [];
3// First transport
4transports.push(nodemailer.createTransport({/*...*/});
5// Second transport
6transports.push(nodemailer.createTransport({/*...*/});
7// Third transport
8transports.push(nodemailer.createTransport({/*...*/});

Create mail-time Server, it is able to send and add emails to queue. On single-server setup - use Server instance to put emails into the queue and actually send them

1import { Mongo }    from 'meteor/mongo';
2import { MailTime } from 'meteor/ostrio:mailer';
3
4const MailQueue = new MailTime({
5  db: Meteor.users.rawDatabase(), // We just want to attain the database connection
6  type: 'server',
7  strategy: 'balancer', // Transports will be used in round robin chain
8  transports,
9  from(transport) {
10    // To pass spam-filters `from` field should be correctly set
11    // for each transport, check `transport` object for more options
12    return `"Awesome App" <${transport.options.from}>`;
13  },
14  concatEmails: true, // Concatenate emails to the same addressee
15  concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
16  template: MailTime.Template // Use default template
17});

Create the Client to add emails to queue from other application units, like UI unit:

1import { MailTime } from 'meteor/ostrio:mailer';
2
3const MailQueue = new MailTime({
4  db: Mongo.Collection('__mailTimeQueue__').rawDatabase(),
5  type: 'client',
6  strategy: 'balancer', // Transports will be used in round robin chain
7  concatEmails: true // Concatenate emails to the same address
8});

Send email:

1MailQueue.sendMail({
2  to: 'user@gmail.com',
3  subject: 'You\'ve got an email!',
4  text: 'Plain text message',
5  html: '<h1>HTML</h1><p>Styled message</p>'
6});

API

new MailTime(opts) constructor

  • opts {Object} - Configuration object
    • opts.db {Db} - Raw MongoDB connection instance. For example: Mongo.Collection('anyCollectionName').rawDatabase() or Meteor.users.rawDatabase()
    • opts.type {String} - client or server, default - server
    • opts.from {Function} - A function which returns String of from field, format: "MyApp" <user@example.com>
    • opts.transports {Array} - An array of nodemailer's transports, returned from nodemailer.createTransport({})
    • opts.strategy {String} - backup or balancer, default - backup. If set to backup, first transport will be used unless failed to send failsToNext times. If set to balancer - transports will be used equally in round robin chain
    • opts.failsToNext {Number} - After how many failed "send attempts" switch to next transport, applied only for backup strategy, default - 4
    • opts.prefix {String} - Use unique prefixes to create multiple MailTime instances on same MongoDB
    • opts.maxTries {Number} - How many times resend failed emails, default - 60
    • opts.interval {Number} - Interval in seconds between send re-tries, default - 60
    • opts.concatEmails {Boolean} - Concatenate email by to field, default - false
    • opts.concatSubject {String} - Email subject used in concatenated email, default - Multiple notifications
    • opts.concatDelimiter {String} - HTML or plain string delimiter used between concatenated email, default - <hr>
    • opts.concatThrottling {Number} - Time in seconds while emails waiting to be concatenated, default - 60
    • opts.debug {Boolean} - Print queue logs, default - false
    • opts.template {String} - Mustache-like template, default - {{{html}}}, all options passed to sendMail is available in Template, like to, subject, text, html or any other custom option. Use {{opt}} for string placeholders and {{{opt}}} for html placeholders

sendMail(opts [, callback])

  • Alias - send()
  • opts {Object} - Configuration object
    • opts.sendAt {Date} - When email should be sent, default - new Date() use with caution on multi-server setup at different location with the different time-zones
    • opts.template - Email specific template, this will override default template passed to MailTime constructor
    • opts.concatSubject - Email specific concatenation subject, this will override default concatenation subject passed to MailTime constructor
    • opts[key] {Mix} - Other custom and NodeMailer specific options, like text, html and to, see more here. Note attachments should work only via path, and file must exists on all micro-services servers
  • callback {Function} - Callback after the email was sent or failed to be sent. Do not use on multi-server setup

static MailTime.Template

Simple and bulletproof HTML template, see its source. Usage:

1// Make it default
2const MailQueue = new MailTime({
3  /* .. */
4  template: MailTime.Template
5});
6
7// For single letter
8MailQueue.sendMail({
9  to: 'user@gmail.com',
10  /* .. */
11  template: MailTime.Template
12});

Template Example

1MailQueue.sendMail({
2  to: 'user@gmail.com',
3  userName: 'Mike',
4  subject: 'Sign up confirmation',
5  text: 'Hello {{userName}}, \r\n Thank you for registration \r\n Your login: {{to}}',
6  html: '<div style="text-align: center"><h1>Hello {{userName}}</h1><p><ul><li>Thank you for registration</li><li>Your login: {{to}}</li></ul></p></div>',
7  template: '<body>{{{html}}}</body>'
8});

Testing

meteor test-packages ./ --driver-package=meteortesting:mocha
# Be patient, tests are taking around 2 mins

Support this project:

This project wouldn't be possible without ostr.io.

Using ostr.io you are not only protecting domain names, monitoring websites and servers, using Prerendering for better SEO of your JavaScript website, but support our Open Source activity, and great packages like this one could be available for free.