├── .gitignore ├── README.md ├── example_templates ├── README.md └── en │ ├── password_reset-body-html.ejs │ ├── password_reset-subject.ejs │ ├── password_reset_help-body-html.ejs │ ├── password_reset_help-subject.ejs │ ├── welcome-body-html.ejs │ └── welcome-subject.ejs ├── package.json ├── src ├── email.js ├── index.js ├── transport.js └── utils │ └── env.js ├── test ├── _fetch.js ├── spec.js └── templates │ └── en │ ├── ejsbody-body-html.ejs │ ├── ejsbody-body-text.ejs │ ├── ejsbody-subject.ejs │ ├── pugbody-body-html.pug │ └── pugbody-subject.ejs └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | 65 | # End of https://www.gitignore.io/api/node 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Email service 2 | 3 | Email microservice that sends emails based on templates. Can be used as a standalone web service or as an express router. 4 | 5 | ## Running as a command line application 6 | 7 | The npm package configures an `pnp-email-service` executable. You will pass configuration options 8 | through ENV variables. Check the configuration options below. 9 | 10 | ## Running as a standalone HTTP server via API 11 | 12 | This is the recommended method for running the microservice via API. You can ignore the `MICROSERVICE_PORT` configuration and this will spin up a server at a random port. Then you can obtain the port the server is running by calling `server.address().port`. This way the microservice is not exposed in the same port than your main application and you are sure it will run in an available port. 13 | 14 | ```javascript 15 | const emailService = require('pnp-email-service') 16 | const config = { 17 | /* Check the configuration options below */ 18 | } 19 | const server = emailService.startServer(config, () => { 20 | const port = server.address().port 21 | console.log(`Listening on port ${port}! Send an HTTP POST to http://127.0.0.1:${port}/email/send for sending an email`) 22 | }) 23 | ``` 24 | 25 | ## Running as an express router 26 | 27 | ```javascript 28 | const emailService = require('pnp-email-service') 29 | const config = { 30 | /* Check the configuration options below */ 31 | } 32 | const router = emailService.createRouter(config) 33 | app.use('/email', router) 34 | ``` 35 | 36 | ## Invoking 37 | 38 | Invoking the service is as simple as doing an HTTP POST request to `{baseURL}/send`. The `baseURL` depends on how you are deploying the service. For example if you are running it as an express router mounted in `/email` in a server running at `127.0.0.1:3000` the URL will be: `http(s)://127.0.0.1:3000/email/send`. 39 | 40 | You need to send a JSON body with the following structure: 41 | 42 | ```javascript 43 | { 44 | "language": "en", 45 | "templateName": "welcome", 46 | "templateOptions": { 47 | "user": { 48 | "name": "John" 49 | } 50 | }, 51 | "emailOptions": { 52 | "from": "Judy ", 53 | "to": "John " 54 | } 55 | } 56 | ``` 57 | 58 | If your `{lang}/{templateName}-body-html.ejs` template has this content: 59 | 60 | ```html 61 | 64 |

Welcome <%= user.name %>

65 |

Cheers,

66 | ``` 67 | 68 | This HTML content will be sent: 69 | 70 | ```html 71 |

Welcome John

72 |

Cheers,

73 | ``` 74 | 75 | ### Full example 76 | 77 | The following code uses `node-fetch` as HTTP client library. It spins an HTTP server and provides a simple `sendEmail()` function: 78 | 79 | ```javascript 80 | const fetch = require('node-fetch') 81 | const emailService = require('pnp-email-service') 82 | const emailServer = emailService.startServer(config) 83 | 84 | const sendEmail = (templateName, emailOptions, templateOptions, language) => { 85 | const port = emailServer.address().port 86 | const url = `http://127.0.0.1:${port}/email/send` 87 | const body = { templateName, emailOptions, templateOptions, language } 88 | return fetch(url, { 89 | method: 'POST', 90 | body: JSON.stringify(body), 91 | headers: { 'Content-Type': 'application/json' } 92 | }) 93 | } 94 | 95 | // Example usage passing a `user` object to the template 96 | sendEmail('welcome', { to: email }, { user }) 97 | .then(response => console.log('Email sent')) 98 | .catch(err => console.error(err.stack)) 99 | ``` 100 | 101 | ## Configuration options 102 | 103 | All configuration options can be configured using ENV variables. If using it as an express router, then configuration variables can also be passed as an argument to this method. All ENV variables can be prefixed with `EMAIL_`. Since one value can be configured in many ways some take precedence over others. For example for the `DEFAULT_FROM` variable the value used will be the first found following this list: 104 | 105 | - `EMAIL_DEFAULT_FROM` parameter passed to `createRouter()` or `startServer()` 106 | - `DEFAULT_FROM` parameter passed to `createRouter()` or `startServer()` 107 | - `EMAIL_DEFAULT_FROM` ENV variable 108 | - `DEFAULT_FROM` ENV variable 109 | 110 | This is the list of available configuration options: 111 | 112 | | Variable | Description | 113 | | --- | --- | 114 | | `MICROSERVICE_PORT` | Port number for the standalone application. If not specified it will run in a random port | 115 | | `DEFAULT_FROM` | Default email sender if a `from` parameter is not specified | 116 | | `DEFAULT_LANGUAGE` | Default language to be used if a `language` is not specified. Defaults to `en` | 117 | | `TRANSPORT` | Third-party service to be used to send the email. Supported values: [`ses`, `sendgrid`, `postmark`, `mailgun`, 'smtp'] for production; `stub` for testing | 118 | | `AWS_KEY` | AWS Key for sending emails using Amazon SES | 119 | | `AWS_SECRET` | AWS Secret for sending emails using Amazon SES | 120 | | `AWS_REGION` | AWS Region for sending emails using Amazon SES | 121 | | `SENDGRID_API_KEY` | API Key for sending emails when using Sendgrid | 122 | | `POSTMARK_API_KEY` | API Key for sending emails when using Postmark | 123 | | `MAILGUN_API_KEY` | API Key for sending emails when using Mailgun | 124 | | `MAILGUN_DOMAIN` | Domain name from which emails are sent when using Mailgun | 125 | | `SMTP_HOST` | SMTP host from which emails are sent when using SMTP | 126 | | `SMTP_PORT` | SMTP port from which emails are sent when using SMTP | 127 | | `SMTP_SECURE` | SMTP TLS from which emails are sent when using SMTP ("true"/"false") | 128 | | `SMTP_USER` | SMTP user from which emails are sent when using SMTP | 129 | | `SMTP_PASS` | SMTP password from which emails are sent when using SMTP | 130 | | `TEMPLATES_DIR` | Absolute path to directory where templates will be found | 131 | 132 | ## Templates 133 | 134 | The service will use the following templates: 135 | 136 | - `{lang}/{templateName}-body-text.ejs` if available it will be used as plain text version of the message 137 | - `{lang}/{templateName}-subject.ejs` for the email subject 138 | 139 | For the HTML body one of these will be used: 140 | 141 | - `{lang}/{templateName}-body-html.ejs` HTML template using [EJS](http://ejs.co) 142 | - `{lang}/{templateName}-body-html.pug` HTML template using [PUG](https://pugjs.org) 143 | 144 | The HTML output of the template is passed through [juice](https://github.com/Automattic/juice) for inlining the CSS styles. 145 | 146 | ## Example templates 147 | 148 | There are a few example templates available in the `example_templates` directory of the repo. 149 | 150 | ## Testing your templates 151 | 152 | You can test your templates from the command line using tools such as [ejs-cli](https://www.npmjs.com/package/ejs-cli). For example: 153 | 154 | ```bash 155 | ejs-cli example_templates/en/password_reset-body-html.ejs -O '{"name":"John","action_url":"http://","operating_system":"","browser_name":"","supp 156 | ort_url":""}' > password_reset.html 157 | ``` 158 | 159 | Or you can specify `TRANSPORT=stub`. This way no real emails will be sent and you will get the rendered templates as response when invoking the service. 160 | -------------------------------------------------------------------------------- /example_templates/README.md: -------------------------------------------------------------------------------- 1 | # Example templates 2 | 3 | The templates in this directory are based on these [Rock-solid email templates](https://github.com/wildbit/postmark-templates). 4 | -------------------------------------------------------------------------------- /example_templates/en/password_reset-body-html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Set up a new password for [Product Name] 7 | 12 | 389 | 390 | 391 | Use this link to reset your password. The link is only valid for 24 hours. 392 | 393 | 394 | 469 | 470 | 471 | 472 | 473 | -------------------------------------------------------------------------------- /example_templates/en/password_reset-subject.ejs: -------------------------------------------------------------------------------- 1 | Set up a new password for [Product Name] 2 | -------------------------------------------------------------------------------- /example_templates/en/password_reset_help-body-html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Set up a new password for [Product Name] 7 | 12 | 389 | 390 | 391 | We received a request to reset your password with this email address. (<%= email_address %>) 392 | 393 | 394 | 469 | 470 | 471 | 472 | 473 | -------------------------------------------------------------------------------- /example_templates/en/password_reset_help-subject.ejs: -------------------------------------------------------------------------------- 1 | Set up a new password for [Product Name] 2 | -------------------------------------------------------------------------------- /example_templates/en/welcome-body-html.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Welcome to [Product Name], <%= name %>! 7 | 12 | 389 | 390 | 391 | Thanks for trying out [Product Name]. We’ve pulled together some information and resources to help you get started. 392 | 393 | 394 | 501 | 502 | 503 | 504 | 505 | -------------------------------------------------------------------------------- /example_templates/en/welcome-subject.ejs: -------------------------------------------------------------------------------- 1 | Welcome to [Product Name], <%- name %> 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pnp-email-service", 3 | "version": "0.1.8", 4 | "description": "Email microservice that sends emails based on templates", 5 | "author": "Clevertech ", 6 | "license": "MIT", 7 | "bugs": { 8 | "url": "https://github.com/clevertech/email-service/issues" 9 | }, 10 | "homepage": "https://github.com/clevertech/email-service#readme", 11 | "main": "src/index.js", 12 | "scripts": { 13 | "start": "better-npm-run start", 14 | "start-dev": "nodemon", 15 | "test": "MICROSERVICE_PORT=3001 ava --verbose --fail-fast", 16 | "lint": "standard" 17 | }, 18 | "betterScripts": { 19 | "start": "node src/index.js", 20 | "start-dev": "nodemon src/index.js" 21 | }, 22 | "bin": { 23 | "pnp-email-service": "./src/index.js" 24 | }, 25 | "dependencies": { 26 | "aws-sdk": "^2.12.0", 27 | "better-npm-run": "0.0.15", 28 | "body-parser": "^1.17.1", 29 | "ejs": "^2.5.6", 30 | "express": "^4.13.3", 31 | "joi": "^10.2.2", 32 | "juice": "^4.0.2", 33 | "nodemailer": "^4.0.1", 34 | "nodemailer-mailgun-transport": "^1.3.5", 35 | "nodemailer-postmark-transport": "^1.2.0", 36 | "nodemailer-sendgrid-transport": "^0.2.0", 37 | "nodemailer-ses-transport": "^1.5.1", 38 | "nodemailer-stub-transport": "^1.1.0", 39 | "pug": "^2.0.0-beta11", 40 | "winston": "*" 41 | }, 42 | "devDependencies": { 43 | "ava": "^0.18.1", 44 | "node-fetch": "^1.6.3", 45 | "nodemon": "^1.11.0", 46 | "standard": "^8.6.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/email.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk') 2 | const juice = require('juice') 3 | const pug = require('pug') 4 | const ejs = require('ejs') 5 | const path = require('path') 6 | const fs = require('fs') 7 | const winston = require('winston') 8 | 9 | const createTransport = require('./transport') 10 | 11 | module.exports = env => { 12 | const defaultLanguage = env('DEFAULT_LANGUAGE', 'en') 13 | const defaultEmailFrom = env('DEFAULT_FROM') 14 | const templatesDir = env('TEMPLATES_DIR') 15 | const transporter = createTransport(env) 16 | const service = {} 17 | 18 | const fullPath = relativePath => path.join(templatesDir, relativePath) 19 | 20 | const fileExists = fullPath => new Promise((resolve, reject) => { 21 | if (!fullPath.startsWith(templatesDir + path.sep)) return reject(new Error('Invalid template path')) 22 | fs.access(fullPath, fs.constants.R_OK, err => resolve(!err)) 23 | }) 24 | 25 | const renderEjs = (filename, data) => new Promise((resolve, reject) => { 26 | ejs.renderFile(filename, data, {}, (err, str) => { 27 | err ? reject(err) : resolve(str) 28 | }) 29 | }) 30 | 31 | const renderPug = (filename, data) => new Promise((resolve, reject) => { 32 | resolve(pug.renderFile(filename, data)) 33 | }) 34 | 35 | const processIfExists = (filename, data, func) => fileExists(filename).then(exists => exists ? func(filename, data) : void 0) 36 | 37 | service.processTemplate = (template, templateOptions, lang = defaultLanguage) => { 38 | const pathEjsHtmlBody = fullPath(`${lang}/${template}-body-html.ejs`) 39 | const pathPugHtmlBody = fullPath(`${lang}/${template}-body-html.pug`) 40 | const pathEjsTextBody = fullPath(`${lang}/${template}-body-text.ejs`) 41 | const pathEjsSubject = fullPath(`${lang}/${template}-subject.ejs`) 42 | 43 | return Promise.all([ 44 | processIfExists(pathEjsHtmlBody, templateOptions, renderEjs), 45 | processIfExists(pathPugHtmlBody, templateOptions, renderPug), 46 | processIfExists(pathEjsTextBody, templateOptions, renderEjs), 47 | processIfExists(pathEjsSubject, templateOptions, renderEjs) 48 | ]) 49 | .then(([ejsHtmlBody, pugHtmlBody, ejsTextBody, ejsSubject]) => { 50 | return { 51 | subject: (ejsSubject || '').trim(), 52 | html: juice(ejsHtmlBody || pugHtmlBody || ''), 53 | text: ejsTextBody 54 | } 55 | }) 56 | } 57 | 58 | /* 59 | // Available mailOptions 60 | const mailOptions = { 61 | from: 'Fred Foo ✔ ', // sender address 62 | to: 'bar@blurdybloop.com, baz@blurdybloop.com', // list of receivers 63 | subject: 'Hello ✔', // Subject line 64 | text: 'Hello world ✔', // plaintext body 65 | html: 'Hello world ✔' // html body 66 | }; 67 | */ 68 | service.sendMail = (mailOptions) => { 69 | mailOptions.from = mailOptions.from || defaultEmailFrom 70 | return new Promise((resolve, reject) => { 71 | transporter.sendMail(mailOptions, (err, info) => { 72 | if (err) { 73 | reject(err) 74 | return winston.error(err) 75 | } 76 | resolve(info) 77 | }) 78 | }) 79 | } 80 | 81 | service.sendTemplatedEmail = (mailOptions, template, templateOptions, lang) => { 82 | return service.processTemplate(template, templateOptions, lang) 83 | .then(opts => { 84 | const options = Object.assign({}, mailOptions, opts) 85 | return service.sendMail(options) 86 | }) 87 | } 88 | 89 | return service 90 | } 91 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const winston = require('winston') 6 | 7 | const Joi = require('joi') 8 | 9 | const schema = Joi.object().keys({ 10 | templateName: Joi.string().required(), 11 | templateOptions: Joi.object().required(), 12 | emailOptions: Joi.object().required(), 13 | language: Joi.string() 14 | }) 15 | 16 | exports.createRouter = (config = {}) => { 17 | const env = require('./utils/env')(config) 18 | const service = require('./email')(env) 19 | 20 | const router = express.Router() 21 | router.use(bodyParser.json({})) 22 | router.post('/send', (req, res, next) => { 23 | const { body } = req 24 | const result = Joi.validate(body, schema) 25 | if (result.error) { 26 | return res.status(400).json({ 27 | error: result.error.message || String(result.error) 28 | }) 29 | } 30 | const { 31 | emailOptions, 32 | templateName, 33 | templateOptions, 34 | language 35 | } = req.body 36 | service.sendTemplatedEmail(emailOptions, templateName, templateOptions, language) 37 | .then(response => res.json(response)) 38 | .catch(err => res.status(500).json({ error: err.message || String(err) })) 39 | }) 40 | return router 41 | } 42 | 43 | exports.startServer = (config, callback) => { 44 | const env = require('./utils/env')(config) 45 | const app = express() 46 | const router = exports.createRouter(config) 47 | const port = +env('MICROSERVICE_PORT') || 0 48 | 49 | app.use('/email', router) 50 | 51 | app.get('/healthz', (req, res) => { 52 | res.status(200).send({ 'status': 'OK' }) 53 | }) 54 | 55 | app.get('/robots.txt', (req, res) => { 56 | res.type('text/plain') 57 | const pattern = process.env.ROBOTS_INDEX === 'true' ? '' : ' /' 58 | res.send(`User-agent: *\nDisallow:${pattern}\n`) 59 | }) 60 | 61 | return app.listen(port, callback) 62 | } 63 | 64 | if (require.main === module) { 65 | const server = exports.startServer({}, () => { 66 | const address = server.address() 67 | winston.info('NODE_ENV: ' + process.env.NODE_ENV) 68 | winston.info(`Listening on port ${address.port}! Send an HTTP POST to http://${address.address}:${address.port}/email/send for sending an email`) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/transport.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | const ses = require('nodemailer-ses-transport') 3 | const sendgrid = require('nodemailer-sendgrid-transport') 4 | const postmark = require('nodemailer-postmark-transport') 5 | const mailgun = require('nodemailer-mailgun-transport') 6 | const stub = require('nodemailer-stub-transport') 7 | const winston = require('winston') 8 | 9 | module.exports = env => { 10 | switch (env('TRANSPORT')) { 11 | case 'ses': 12 | const AWS = require('aws-sdk') 13 | AWS.config.update({ 14 | accessKeyId: env('AWS_KEY'), 15 | secretAccessKey: env('AWS_SECRET'), 16 | region: env('AWS_REGION') 17 | }) 18 | return nodemailer.createTransport(ses({ ses: new AWS.SES() })) 19 | case 'sendgrid': 20 | return nodemailer.createTransport(sendgrid({ 21 | auth: { 22 | api_key: env('SENDGRID_API_KEY') 23 | } 24 | })) 25 | case 'postmark': 26 | return nodemailer.createTransport(postmark({ 27 | auth: { 28 | apiKey: env('POSTMARK_API_KEY') 29 | } 30 | })) 31 | case 'mailgun': 32 | return nodemailer.createTransport(mailgun({ 33 | auth: { 34 | api_key: env('MAILGUN_API_KEY'), 35 | domain: env('MAILGUN_DOMAIN') 36 | } 37 | })) 38 | case 'smtp': 39 | return nodemailer.createTransport({ 40 | host: env('SMTP_HOST'), 41 | port: env('SMTP_PORT'), 42 | secure: (env('SMTP_SECURE') == 'true'), // eslint-disable-line eqeqeq 43 | ignoreTLS: (env('SMTP_SECURE') == 'false'), // eslint-disable-line eqeqeq 44 | auth: { 45 | user: env('SMTP_USER'), 46 | pass: env('SMTP_PASS') 47 | } 48 | }) 49 | case 'stub': 50 | return nodemailer.createTransport(stub()) 51 | default: 52 | winston.error('No valid TRANSPORT set') 53 | return nodemailer.createTransport() // direct transport 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/utils/env.js: -------------------------------------------------------------------------------- 1 | const prefix = 'EMAIL_' 2 | const defaults = {} 3 | 4 | module.exports = (env = {}) => (key, defaultValue) => { 5 | return [ 6 | env[prefix + key], 7 | env[key], 8 | process.env[prefix + key], 9 | process.env[key], 10 | defaultValue, 11 | defaults[key] 12 | ].find(value => value != null) 13 | } 14 | -------------------------------------------------------------------------------- /test/_fetch.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | const baseURL = `http://127.0.0.1:${process.env.MICROSERVICE_PORT}` 3 | 4 | require('../').startServer() 5 | 6 | module.exports = (path, options) => { 7 | const body = options && options.body 8 | if (Object.prototype.toString.call(body) === '[object Object]') { 9 | options.body = JSON.stringify(body) 10 | options.headers = Object.assign({}, options.headers, { 11 | 'Content-Type': 'application/json' 12 | }) 13 | } 14 | return fetch(baseURL + path, options) 15 | } 16 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const test = require('ava') 3 | 4 | process.env.TEMPLATES_DIR = path.join(__dirname, 'templates') 5 | process.env.TRANSPORT = 'stub' 6 | const fetch = require('./_fetch') 7 | 8 | const env = require('../src/utils/env')() 9 | const email = require('../src/email')(env) 10 | 11 | test('render template with ejs body', t => { 12 | return email.processTemplate('ejsbody', { user: { name: 'John' } }, 'en') 13 | .then(response => { 14 | t.deepEqual(response, { 15 | subject: 'Welcome John', 16 | html: '\n\n

Welcome John

\n\n

Cheers,

\n', 17 | text: 'Welcome John\n\nCheers,\n' 18 | }) 19 | }) 20 | }) 21 | 22 | test('fail when suspicious path', t => { 23 | return email.processTemplate('../../package.json', { user: { name: 'John' } }, 'en') 24 | .then(response => t.fail()) 25 | .catch(err => { 26 | t.truthy(err.message === 'Invalid template path') 27 | }) 28 | }) 29 | 30 | test('render template with pug body', t => { 31 | return email.processTemplate('pugbody', { user: { name: 'John' } }, 'en') 32 | .then(response => { 33 | t.deepEqual(response, { 34 | subject: 'Welcome John', 35 | html: '

Welcome John

Cheers,

', 36 | text: undefined 37 | }) 38 | }) 39 | }) 40 | 41 | test('send templated email', t => { 42 | const emailOptions = { 43 | from: 'Judy ', 44 | to: 'John ' 45 | } 46 | return email.sendTemplatedEmail(emailOptions, 'pugbody', { user: { name: 'John' } }, 'en') 47 | .then(info => { 48 | const rawContent = info.response.toString() 49 | t.truthy(rawContent.indexOf('Welcome John') >= 0) 50 | }) 51 | }) 52 | 53 | test('successful rest API call', t => { 54 | return fetch('/email/send', { 55 | method: 'POST', 56 | body: { 57 | language: 'en', 58 | templateName: 'pugbody', 59 | templateOptions: { 60 | user: { name: 'John' } 61 | }, 62 | emailOptions: { 63 | from: 'Judy ', 64 | to: 'John ' 65 | } 66 | } 67 | }) 68 | .then(response => { 69 | t.is(response.status, 200) 70 | return response.json() 71 | }) 72 | .then(body => { 73 | t.truthy(body.messageId) 74 | t.deepEqual(body.envelope, { from: 'judy@example.com', to: [ 'john@example.com' ] }) 75 | }) 76 | }) 77 | 78 | test('failed rest API call', t => { 79 | return fetch('/email/send', { 80 | method: 'POST', 81 | body: { 82 | language: 'en', 83 | templateName: 'pugbody', 84 | templateOptions: {}, 85 | emailOptions: { 86 | from: 'Judy ', 87 | to: 'John ' 88 | } 89 | } 90 | }) 91 | .then(response => { 92 | t.is(response.status, 500) 93 | return response.json() 94 | }) 95 | .then(body => { 96 | t.truthy(body.error.indexOf('Cannot read property \'name\' of undefined') > 0) 97 | }) 98 | }) 99 | 100 | test('bad API call request', t => { 101 | return fetch('/email/send', { 102 | method: 'POST', 103 | body: {} 104 | }) 105 | .then(response => { 106 | t.is(response.status, 400) 107 | return response.json() 108 | }) 109 | .then(body => { 110 | t.truthy(body.error.indexOf('"templateName" is required') > 0) 111 | }) 112 | }) 113 | -------------------------------------------------------------------------------- /test/templates/en/ejsbody-body-html.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Welcome <%= user.name %>

6 | 7 |

Cheers,

8 | -------------------------------------------------------------------------------- /test/templates/en/ejsbody-body-text.ejs: -------------------------------------------------------------------------------- 1 | Welcome <%= user.name %> 2 | 3 | Cheers, 4 | -------------------------------------------------------------------------------- /test/templates/en/ejsbody-subject.ejs: -------------------------------------------------------------------------------- 1 | Welcome <%- user.name %> 2 | -------------------------------------------------------------------------------- /test/templates/en/pugbody-body-html.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | style. 4 | h1 { color: #777 } 5 | body 6 | h1 Welcome #{user.name} 7 | p Cheers, 8 | -------------------------------------------------------------------------------- /test/templates/en/pugbody-subject.ejs: -------------------------------------------------------------------------------- 1 | Welcome <%- user.name %> 2 | --------------------------------------------------------------------------------