Security News
tea.xyz Spam Plagues npm and RubyGems Package Registries
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
mail-time
Advanced tools
Readme
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.
"Mail-Time" is a micro-service package for mail queue, with Server and Client APIs. Build on top of the nodemailer
package.
Every MailTime
instance can get configured as Server or Client.
The main difference between Server and Client modes is that the Server handles the queue and sends email. While the Client only adds emails into the queue.
Redundant solution for email transmission.
Issue - classic solution with a single point of failure:
|----------------| |------| |------------------|
| Other mailer | ------> | SMTP | ------> | ^_^ Happy user |
|----------------| |------| |------------------|
The scheme 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 ------/
Backup scheme with multiple SMTP providers
|--------|
/--X--| SMTP 1 |
/ ^ |--------|
/ \--- Retry with next provider
|----------------|/ |--------| |------------------|
| Mail Time | ---X--> | SMTP 2 | /->| ^_^ Happy user |
|----------------|\ ^ |--------| / |------------------|
\ \--- Retry /
\ |--------| /
\---->| SMTP 3 |--/
|--------|
It is common to create a "Cluster" of servers to balance the load and add a durability layer for horizontal scaling of quickly growing applications.
Most modern application has scheduled or recurring emails. For example, once a day — with recent news and updates. It won't be an issue with a single server setup — the server would send emails at a daily interval via timer or CRON. While in "Cluster" implementation — each server will attempt to send the same email. In such cases, users will receive multiple emails with the same content. We built MailTime to address this and other similar issues.
Here is how this issue is solved by using MailTime:
|===================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 | | | | | |--------|
|=================================================| |=======| |===============|
backup
and balancing
. This is the most useful feature — allowing to reduce the cost of SMTP services and add extra layer of durability. If one transport failing to send an email mail-time
will switch to the next oneTo implement Server functionality — as a first step install nodemailer
, although this package meant to be used with nodemailer
, it's not added as the dependency, as nodemailer
not needed by Client, and you're free to choose nodemailer
's version to fit your project needs:
npm install --save nodemailer
Install MailTime package:
# for node@>=8.9.0
npm install --save mail-time
# for node@<8.9.0
npm install --save mail-time@=0.1.7
Require package:
const MailTime = require('mail-time');
Create nodemailer's transports (see nodemailer
docs):
const transports = [];
const nodemailer = require('nodemailer');
// Use DIRECT transport
// and enable sending email from localhost
// install "nodemailer-direct-transport" NPM package:
const directTransport = require('nodemailer-direct-transport');
const directTransportOpts = {
pool: false,
direct: true,
name: 'mail.example.com',
from: 'no-reply@example.com',
};
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
// IMPORTANT: Copy-paste passed options from directTransport() to
// transport's "options" property, to make sure it's available to MailTime package:
transports[0].options = directTransportOpts;
// Private SMTP
transports.push(nodemailer.createTransport({
host: 'smtp.example.com',
from: 'no-reply@example.com',
auth: {
user: 'no-reply',
pass: 'xxx'
},
}));
// Google Apps SMTP
transports.push(nodemailer.createTransport({
host: 'smtp.gmail.com',
from: 'no-reply@mail.example.com',
auth: {
user: 'no-reply@mail.example.com',
pass: 'xxx'
},
}));
// Mailing service (SparkPost as example)
transports.push(nodemailer.createTransport({
host: 'smtp.sparkpostmail.com',
port: 587,
from: 'no-reply@mail2.example.com',
auth: {
user: 'SMTP_Injection',
pass: 'xxx'
},
}));
As the next step initiate mail-time
in the Server mode, it will be able to send and add emails to the queue. Connecting to a MongoDB before initiating new MailTime
instance:
const MongoClient = require('mongodb').MongoClient;
const MailTime = require('mail-time');
const dbName = 'DatabaseName';
// We're using environment variable MONGO_URL
// to store connection string to MongoDB
// example: "MONGO_URL=mongodb://127.0.0.1:27017/myapp node mail-micro-service.js"
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
const db = client.db(dbName);
const mailQueue = new MailTime({
db, // MongoDB
type: 'server',
strategy: 'balancer', // Transports will be used in round robin chain
transports,
from(transport) {
// To pass spam-filters `from` field should be correctly set
// for each transport, check `transport` object for more options
return `"Awesome App" <${transport.options.from}>`;
},
concatEmails: true, // Concatenate emails to the same addressee
concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
template: MailTime.Template // Use default template
});
});
Only one MailTime
Server instance required to send email. In the other parts of an app (like UI units or in sub-apps) use mail-time
in the Client mode to add emails to queue:
const MongoClient = require('mongodb').MongoClient;
const MailTime = require('mail-time');
const dbName = 'DatabaseName';
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
const db = client.db(dbName);
const mailQueue = new MailTime({
db,
type: 'client',
strategy: 'balancer', // Transports will be used in round robin chain
concatEmails: true // Concatenate emails to the same address
});
});
Send email example:
mailQueue.sendMail({
to: 'user@gmail.com',
subject: 'You\'ve got an email!',
text: 'Plain text message',
html: '<h1>HTML</h1><p>Styled message</p>'
});
MailTime
instances usage exampleCreate two MailTime
instances with different settings.
// USE FOR NON-URGENT EMAILS WHICH IS OKAY TO CONCATENATE
const mailQueue = new MailTime({
db: db,
interval: 35,
strategy: 'backup',
failsToNext: 1,
concatEmails: true,
concatThrottling: 16,
zombieTime: 120000
});
// USE FOR TRANSACTIONAL EMAILS AND ALERTS
const mailInstantQueue = new MailTime({
db: db,
prefix: 'instant',
interval: 2,
strategy: 'backup',
failsToNext: 1,
concatEmails: false,
zombieTime: 20000
});
mailQueue.sendMail({
to: 'user@gmail.com',
subject: 'You\'ve got an email!',
text: 'Plain text message',
html: '<h1>HTML</h1><p>Styled message</p>'
});
mailInstantQueue.sendMail({
to: 'user@gmail.com',
subject: 'Sign in request',
text: 'Your OTP login code: xxxx:',
html: '<h1>Code:</h1><code>XXXX</code>'
});
All options passed to the .sendMail()
method is available inside text
, html
, and global templates
const templates = {
global: '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>{{subject}}</title></head><body>{{html}}<footer>Message sent to @{{username}} user ({{to}})</footer></body></html>',
signInCode: {
text: 'Hello @{{username}}! Here\'s your login code: {{code}}'
html: `<h1>Sign-in request</h1><p>Hello @{{username}}! Copy your login code below <pre><code>{{code}}</code></pre>`
}
}
const mailQueue = new MailTime({
db: db,
template: templates.global
});
mailQueue.sendMail({
to: 'user@gmail.com',
subject: 'Sign-in request',
username: 'johndoe',
code: 'XXXXX-YY'
text: templates.signInCode.text,
html: templates.signInCode.html
});
Mail-Time package can be installed and used within Meteor.js via NPM or Atmosphere
Install NPM mail-time
package:
meteor npm install --save mail-time
Meteor.js: ES6 Import NPM package:
import MailTime from 'mail-time';
Install Atmosphere ostrio:mailer
package:
meteor add ostrio:mailer
Meteor.js: ES6 Import atmosphere package:
import MailTime from 'meteor/ostrio:mailer';
mail-time
package usage examples in Meteor.js
import { MongoInternals } from 'meteor/mongo';
import MailTime from 'mail-time';
import nodemailer from 'nodemailer';
// Use DIRECT transport
// To enable sending email from localhost
// install "nodemailer-direct-transport" NPM package:
import directTransport from 'nodemailer-direct-transport';
const transports = [];
const directTransportOpts = {
pool: false,
direct: true,
name: 'mail.example.com',
from: 'no-reply@example.com',
};
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
// IMPORTANT: Copy-paste passed options from directTransport() to
// transport's "options" property, to make sure it's available to MailTime package:
transports[0].options = directTransportOpts;
////////////////////////
// For more transports example see sections above and read nodemailer's docs
////////////////////////
const mailQueue = new MailTime({
db: MongoInternals.defaultRemoteCollectionDriver().mongo.db, // MongoDB
transports,
from(transport) {
// To pass spam-filters `from` field should be correctly set
// for each transport, check `transport` object for more options
return '"Awesome App" <' + transport.options.from + '>';
}
});
All available constructor options and .sendMail()
method API overview
new MailTime(opts)
constructoropts
{Object} - Configuration objectopts.db
{Db} - [Required] Mongo's Db
instance. For example returned in callback of MongoClient.connect()
opts.type
{String} - [Optional] client
or server
, default - server
opts.from
{Function} - [Optional] A function which returns String of from
field, format: "MyApp" <user@example.com>
opts.transports
{Array} - [Optional] An array of nodemailer
's transports, returned from nodemailer.createTransport({})
opts.strategy
{String} - [Optional] 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 chainopts.failsToNext
{Number} - [Optional] After how many failed "send attempts" switch to next transport, applied only for backup
strategy, default - 4
opts.prefix
{String} - [Optional] Use unique prefixes to create multiple MailTime
instances on same MongoDBopts.maxTries
{Number} - [Optional] How many times resend failed emails, default - 60
opts.interval
{Number} - [Optional] Interval in seconds between send re-tries, default - 60
opts.zombieTime
{Number} - [Optional] Time in milliseconds, after this period - pending email will be interpreted as "zombie". This parameter allows to rescue pending email from "zombie mode" in case when: server was rebooted, exception during runtime was thrown, or caused by bad logic, default - 32786
. This option is used by package itself and passed directly to JoSk
packageopts.keepHistory
{Boolean} - [Optional] By default sent emails not stored in the database. Set { keepHistory: true }
to keep queue task as it is in the database, default - false
opts.concatEmails
{Boolean} - [Optional] Concatenate email by to
field, default - false
opts.concatSubject
{String} - [Optional] Email subject used in concatenated email, default - Multiple notifications
opts.concatDelimiter
{String} - [Optional] HTML or plain string delimiter used between concatenated email, default - <hr>
opts.concatThrottling
{Number} - [Optional] Time in seconds while emails are waiting to be concatenated, default - 60
opts.revolvingInterval
{Number} - [Optional] Interval in milliseconds in between queue checks, default - 256
. Recommended value — between opts.minRevolvingDelay
and opts.maxRevolvingDelay
opts.minRevolvingDelay
{Number} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in milliseconds, default - 64
. This option is passed directly to JoSk
packageopts.maxRevolvingDelay
{Number} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in milliseconds, default - 256
. This option is passed directly to JoSk
packageopts.template
{String} - [Optional] 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 placeholderssendMail(opts [, callback])
send()
opts
{Object} - Configuration objectopts.sendAt
{Date} - When email should be sent, default - new Date()
use with caution on multi-server setup at different location with the different time-zonesopts.template
- Email specific template, this will override default template passed to MailTime
constructoropts.concatSubject
- Email specific concatenation subject, this will override default concatenation subject passed to MailTime
constructoropts[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 serverscallback
{Function} - Callback called after the email was sent or failed to be sent. Do not use on multi-server setupstatic MailTime.Template
Simple and bulletproof HTML template, see its source. Usage example:
const MailTime = require('mail-time');
// Make it default
const mailQueue = new MailTime({
db: db, // MongoDB
/* .. */
template: MailTime.Template
});
// For single letter
mailQueue.sendMail({
to: 'user@gmail.com',
/* .. */
template: MailTime.Template
});
Pass custom template via template
property to .sendMail()
method
mailQueue.sendMail({
to: 'user@gmail.com',
userName: 'Mike',
subject: 'Sign up confirmation',
text: 'Hello {{userName}}, \r\n Thank you for registration \r\n Your login: {{to}}',
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>',
template: '<body>{{{html}}}</body>'
});
Test NPM package:
# Before run tests make sure NODE_ENV === development
# Install NPM dependencies
npm install --save-dev
# Before run tests you need to have running MongoDB
DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/npm-mail-time-test-001" npm test
# Be patient, tests are taking around 2 mins
Test Atmosphere (meteor.js) package:
# Default
EMAIL_DOMAIN="example.com" meteor test-packages ./ --driver-package=meteortesting:mocha
# With custom port
DEBUG="true" EMAIL_DOMAIN="example.com" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
# With local MongoDB and custom port
DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/meteor-mail-time-test-001" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
# Be patient, tests are taking around 2 mins
FAQs
📮 Email queue extending NodeMailer with multi SMTP transports and horizontally scaled apps support
The npm package mail-time receives a total of 145 weekly downloads. As such, mail-time popularity was classified as not popular.
We found that mail-time demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
Security News
As cyber threats become more autonomous, AI-powered defenses are crucial for businesses to stay ahead of attackers who can exploit software vulnerabilities at scale.
Security News
UnitedHealth Group disclosed that the ransomware attack on Change Healthcare compromised protected health information for millions in the U.S., with estimated costs to the company expected to reach $1 billion.