├── .babelrc ├── .codeclimate.yml ├── .github ├── contributing.md ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .istanbul.yml ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example └── app.js ├── mocha.opts ├── package.json ├── src ├── hooks │ ├── index.js │ ├── normalize-email.js │ ├── render-template.js │ ├── send-email.js │ └── validate-email.js ├── index.js └── services │ └── mail.js └── test ├── src ├── hooks │ ├── index.test.js │ ├── normalize-email.test.js │ ├── render-template.test.js │ └── validate-email.test.js ├── index.test.js └── services │ └── mail.test.js └── test-app.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["add-module-exports"], 3 | "presets": [ "es2015" ] 4 | } 5 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | JavaScript: true 3 | # exclude_paths: 4 | # - "foo/bar.rb" -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Feathers 2 | 3 | Thank you for contributing to Feathers! :heart: :tada: 4 | 5 | This repo is the main core and where most issues are reported. Feathers embraces modularity and is broken up across many repos. To make this easier to manage we currently use [Zenhub](https://www.zenhub.com/) for issue triage and visibility. They have a free browser plugin you can install so that you can see what is in flight at any time, but of course you also always see current issues in Github. 6 | 7 | ## Report a bug 8 | 9 | Before creating an issue please make sure you have checked out the docs, specifically the [FAQ](https://docs.feathersjs.com/help/faq.html) section. You might want to also try searching Github. It's pretty likely someone has already asked a similar question. 10 | 11 | If you haven't found your answer please feel free to join our [slack channel](http://slack.feathersjs.com), create an issue on Github, or post on [Stackoverflow](http://stackoverflow.com) using the `feathers` or `feathersjs` tag. We try our best to monitor Stackoverflow but you're likely to get more immediate responses in Slack and Github. 12 | 13 | Issues can be reported in the [issue tracker](https://github.com/feathersjs/feathers/issues). Since feathers combines many modules it can be hard for us to assess the root cause without knowing which modules are being used and what your configuration looks like, so **it helps us immensely if you can link to a simple example that reproduces your issue**. 14 | 15 | ## Report a Security Concern 16 | 17 | We take security very seriously at Feathers. We welcome any peer review of our 100% open source code to ensure nobody's Feathers app is ever compromised or hacked. As a web application developer you are responsible for any security breaches. We do our very best to make sure Feathers is as secure as possible by default. 18 | 19 | In order to give the community time to respond and upgrade we strongly urge you report all security issues to us. Send one of the core team members a PM in [Slack](http://slack.feathersjs.com) or email us at hello@feathersjs.com with details and we will respond ASAP. 20 | 21 | For full details refer to our [Security docs](https://docs.feathersjs.com/SECURITY.html). 22 | 23 | ## Pull Requests 24 | 25 | We :heart: pull requests and we're continually working to make it as easy as possible for people to contribute, including a [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) and a [common test suite](https://github.com/feathersjs/feathers-service-tests) for database adapters. 26 | 27 | We prefer small pull requests with minimal code changes. The smaller they are the easier they are to review and merge. A core team member will pick up your PR and review it as soon as they can. They may ask for changes or reject your pull request. This is not a reflection of you as an engineer or a person. Please accept feedback graciously as we will also try to be sensitive when providing it. 28 | 29 | Although we generally accept many PRs they can be rejected for many reasons. We will be as transparent as possible but it may simply be that you do not have the same context or information regarding the roadmap that the core team members have. We value the time you take to put together any contributions so we pledge to always be respectful of that time and will try to be as open as possible so that you don't waste it. :smile: 30 | 31 | **All PRs (except documentation) should be accompanied with tests and pass the linting rules.** 32 | 33 | ### Code style 34 | 35 | Before running the tests from the `test/` folder `npm test` will run ESlint. You can check your code changes individually by running `npm run lint`. 36 | 37 | ### ES6 compilation 38 | 39 | Feathers uses [Babel](https://babeljs.io/) to leverage the latest developments of the JavaScript language. All code and samples are currently written in ES2015. To transpile the code in this repository run 40 | 41 | > npm run compile 42 | 43 | __Note:__ `npm test` will run the compilation automatically before the tests. 44 | 45 | ### Tests 46 | 47 | [Mocha](http://mochajs.org/) tests are located in the `test/` folder and can be run using the `npm run mocha` or `npm test` (with ESLint and code coverage) command. 48 | 49 | ### Documentation 50 | 51 | Feathers documentation is contained in Markdown files in the [feathers-docs](https://github.com/feathersjs/feathers-docs) repository. To change the documentation submit a pull request to that repo, referencing any other PR if applicable, and the docs will be updated with the next release. 52 | 53 | ## External Modules 54 | 55 | If you're written something awesome for Feathers, the Feathers ecosystem, or using Feathers please add it to the [showcase](https://docs.feathersjs.com/why/showcase.html). You also might want to check out the [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) that can be used to scaffold plugins to be Feathers compliant from the start. 56 | 57 | If you think it would be a good core module then please contact one of the Feathers core team members in [Slack](http://slack.feathersjs.com) and we can discuss whether it belongs in core and how to get it there. :beers: 58 | 59 | ## Contributor Code of Conduct 60 | 61 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 62 | 63 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 64 | 65 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 66 | 67 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 68 | 69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 72 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | 3 | (First please check that this issue is not already solved as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#report-a-bug)) 5 | 6 | - [ ] Tell us what broke. The more detailed the better. 7 | - [ ] If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc. 8 | 9 | ### Expected behavior 10 | Tell us what should happen 11 | 12 | ### Actual behavior 13 | Tell us what happens instead 14 | 15 | ### System configuration 16 | 17 | Tell us about the applicable parts of your setup. 18 | 19 | **Module versions** (especially the part that's not working): 20 | 21 | **NodeJS version**: 22 | 23 | **Operating System**: 24 | 25 | **Browser Version**: 26 | 27 | **React Native Version**: 28 | 29 | **Module Loader**: -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | (If you have not already please refer to the contributing guideline as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#pull-requests)) 5 | 6 | - [ ] Tell us about the problem your pull request is solving. 7 | - [ ] Are there any open issues that are related to this? 8 | - [ ] Is this PR dependent on PRs in other repos? 9 | 10 | If so, please mention them to keep the conversations linked together. 11 | 12 | ### Other Information 13 | 14 | If there's anything else that's important and relevant to your pull 15 | request, mention that information here. This could include 16 | benchmarks, or other information. 17 | 18 | Your PR will be reviewed by a core team member and they will work with you to get your changes merged in a timely manner. If merged your PR will automatically be added to the changelog in the next release. 19 | 20 | If your changes involve documentation updates please mention that and link the appropriate PR in [feathers-docs](https://github.com/feathersjs/feathers-docs). 21 | 22 | Thanks for contributing to Feathers! :heart: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # === Feathers NeDB === 2 | db-data 3 | 4 | # === Node === 5 | lib-cov 6 | *.seed 7 | *.log 8 | *.csv 9 | *.dat 10 | *.out 11 | *.pid 12 | *.gz 13 | 14 | pids 15 | logs 16 | results 17 | 18 | npm-debug.log 19 | node_modules 20 | 21 | # === Mac === 22 | .DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must ends with two \r. 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear on external disk 34 | .Spotlight-V100 35 | .Trashes 36 | 37 | # === Vim === 38 | [._]*.s[a-w][a-z] 39 | [._]s[a-w][a-z] 40 | *.un~ 41 | Session.vim 42 | .netrwhist 43 | *~ 44 | 45 | # === Sublime === 46 | # workspace files are user-specific 47 | *.sublime-workspace 48 | 49 | # project files should be checked into the repository, unless a significant 50 | # proportion of contributors will probably not be using SublimeText 51 | # *.sublime-project 52 | 53 | # === Webstorm === 54 | .idea 55 | 56 | lib/ 57 | coverage -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | instrumentation: 3 | root: src/ 4 | excludes: 5 | - lib/ 6 | include-all-sources: true 7 | reporting: 8 | print: summary 9 | reports: 10 | - html 11 | - text 12 | - lcov 13 | watermarks: 14 | statements: [50, 80] 15 | lines: [50, 80] 16 | functions: [50, 80] 17 | branches: [50, 80] -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .codeclimate.yml 3 | .travis.yml 4 | .idea/ 5 | src/ 6 | test/ 7 | !lib/ 8 | .github/ 9 | coverage 10 | .editorconfig 11 | .istanbul.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - '6' 6 | - '4' 7 | before_script: 8 | - 'npm install -g codeclimate-test-reporter' 9 | after_script: 10 | - 'codeclimate-test-reporter < coverage/lcov.info' 11 | addons: 12 | code_climate: 13 | repo_token: 2b65e58e624c6659d9afe6bbaa267adee854269ce95a9bc0f54ee1679809ad11 14 | notifications: 15 | email: false 16 | slack: 17 | rooms: 18 | secure: aXUF/fOsXxi8xdfVHSjrMKJbBqwcJvqPBC/2hTRPTiLM/nbiEdJQ6iTwy0GO0jjSE2g7K3Ch5Vc7SpD06zpxrElHnH3pGb2YfIO3KwlWY7Zp7zv9qq4ta7qeVBcILSRIWofjh36gAVBmIis/t4YjdcKJGqy8bayf1NyF4vg2AfZezXBWfIpbevpv7Wg6d4T4Ne+datzToSbwXDThJHkwEL9RTUVRfS992XuQRgkQPNW+YjODaap+OxnmVBGt2gIOTPUUl9ucozK2ySGyKfl8jCFtqHItOrxccuJWEvtnJptsKbH8jZbHloJ++mctqSUl7KsO74Le8pVz4cnzr9Eqq49oOi8+6EP7l9hqmw0w9wwtGPf0WOxfzUlNCMXIuug/Anjclqakc3ZEMTPcH/KbF4jEhOTmjjHfjN/vQ5ceJ34usuXMPjH1wiN0SaxHj0DHgvhmD5hLfkgCVzDDDxnt9cziFIjez/IpHbBBRNIUKf4HW86sfsn3bXLl6wdNQMLNDmq1qdBIn+ga1hymN/4Y/X78qkQ44he5EZqW+F8DLHWZr++CC4HPZ3fYpqB77yA6C99KweQ8CcZc+7W/hKLrG82XNsIrJZUZSCncziWVFAgrkJYhdKEuye1UMJ60JBGBpWdd2ZmkCYhaNWl4kc8UYfmtEtaUvMzT7KSgrif8bM4= 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.2.0 4 | 5 | - **Breaking changes errrwhere!** 6 | - Adding more tests 7 | - Dropping [sendgrid](https://github.com/sendgrid/sendgrid-nodejs) module due to complexity and instability. Switching to Request instead. 8 | - Upgrading to Sendgrid API v3 9 | - Adding hooks: 10 | - for rendering a template file 11 | - validating email params when sending 12 | - normalizing params for easy sending 13 | 14 | ## v0.1.0 15 | 16 | - Initial Release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 FeathersJS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-sendgrid 2 | 3 | > A [SendGrid](https://sendgrid.com) Service for [FeatherJS](https://github.com/feathersjs). 4 | 5 | ## Unmaintained 6 | 7 | This module is no longer maintained. Use [feathers-mailer](https://github.com/feathersjs-ecosystem/feathers-mailer) with the [nodemailer-sendgrid-transport](https://github.com/sendgrid/nodemailer-sendgrid-transport) instead. 8 | 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install feathers-sendgrid --save 14 | ``` 15 | 16 | ## Documentation 17 | 18 | `feathers-sendgrid` is used just like any other service. In order to send an email simply call `create` with a payload that conforms to the [Sendgrid V3 REST API](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html#-Request-Body-Parameters). You can see [an example payload here](https://github.com/sendgrid/sendgrid-nodejs/blob/master/USAGE.md#post-mailsend). 19 | 20 | For usage with some of the bundled hooks see the example below. 21 | 22 | ## Bundled Hooks 23 | 24 | This module comes with a couple bundled hooks that make it a bit easier to send email. These are **entirely optional**. 25 | 26 | - `renderTemplate`, 27 | - `normalizeEmail`, 28 | - `validateEmail`, 29 | - `sendEmail` 30 | 31 | ### Rendering Email Templates 32 | 33 | This hook renders a specific email `template` based on your express view `engine` with your `hook` object. It is meant to be used as a `before` hook on the a `create` method. 34 | 35 | ```js 36 | const Handlebars = require('hbs'); 37 | const hooks = require('feathers-sendgrid').hooks; 38 | 39 | app.service('mailer').before({ 40 | create: [ 41 | hooks.renderTemplate({ engine: Handlebars }) 42 | ] 43 | }); 44 | 45 | function sendEmail(options = {}) { 46 | return function(hook) { 47 | return new Promise((resolve, reject) => { 48 | hooks.renderTemplate({template: 'welcome', engine: Handlebars })(hook) 49 | .then(hook => { 50 | const data = { 51 | from: 'hello@feathersjs.com', 52 | to: hook.result.email, 53 | subject: 'Welcome', 54 | content: hook.data.content 55 | }; 56 | 57 | hook.app.service('mailer') 58 | .create(data, {template: 'welcome'}) 59 | .then() 60 | }) 61 | }); 62 | }; 63 | } 64 | 65 | app.service('users').after({ 66 | create: [ 67 | sendEmail() 68 | ] 69 | }); 70 | ``` 71 | 72 | #### Options 73 | 74 | - `engine` (**required**) - the view engine instance. 75 | - `template` (**required**) - the name of your template. 76 | - `path` [optional] - path to your email template directory. Defaults to your express view engine path + 'email' (ie. path/to/views/email/). 77 | 78 | ### Validating Email Params 79 | 80 | This hook validates that the following fields exist inside `hook.data`: 81 | 82 | - `from` 83 | - `to` or `personalizations` 84 | - `subject` 85 | - `content` 86 | 87 | It is really loose validation since Sendgrid does it's own validation. This is more for ensuring that the absolute minimum fields are included in order to send an email. 88 | 89 | ```js 90 | const hooks = require('feathers-sendgrid').hooks; 91 | 92 | app.service('mailer').before({ 93 | create: [ 94 | hooks.validateEmail() 95 | ] 96 | }); 97 | ``` 98 | 99 | ### Normalizing Email Params 100 | 101 | This hook makes it a bit less tedious to send simple emails. It takes a simple flat format and turns it into the format that Sendgrid expects. It only effects the following fields in `hook.data`: 102 | 103 | - `from` 104 | - `to` 105 | - `subject` 106 | - `content` 107 | 108 | ```js 109 | const hooks = require('feathers-sendgrid').hooks; 110 | 111 | app.service('mailer').before({ 112 | create: [ 113 | hooks.normalizeEmail() 114 | ] 115 | }); 116 | ``` 117 | 118 | ## Complete Example 119 | 120 | Here's an example of a Feathers server with a `mailer` Sendgrid service. 121 | 122 | ```js 123 | import rest = from 'feathers-rest'; 124 | import hooks from 'feathers-hooks'; 125 | import feathers from 'feathers'; 126 | import bodyParser from 'body-parser'; 127 | import { MailService, hooks as mailerHooks } from 'feathers-sendgrid'; 128 | 129 | // Create a feathers instance. 130 | var app = feathers() 131 | // Enable REST services 132 | .configure(rest()) 133 | // Enable hooks 134 | .configure(hooks()) 135 | // Turn on JSON parser for REST services 136 | .use(bodyParser.json()) 137 | // Turn on URL-encoded parser for REST services 138 | .use(bodyParser.urlencoded({extended: true})); 139 | 140 | // Register the Sendgrid service 141 | app.use('/mailer', MailService({ apiKey: "YOUR_SENDGRID_API_KEY" })); 142 | 143 | app.service('mailer').before({ 144 | create: [mailerHooks.validateEmail(), mailerHooks.normalizeEmail()] 145 | }); 146 | 147 | // Use the service 148 | var email = { 149 | from: 'FROM_EMAIL', 150 | to: 'TO_EMAIL', 151 | subject: 'Sendgrid test', 152 | content: 'This is the email body' 153 | }; 154 | 155 | app.service('mailer').create(email).then(function (result) { 156 | console.log('Sent email', result); 157 | }).catch(err => { 158 | console.log(err); 159 | }); 160 | 161 | // Start the server. 162 | var port = 3030; 163 | app.listen(port, function() { 164 | console.log(`Feathers server listening on port ${port}`); 165 | }); 166 | ``` 167 | 168 | You can run this example by using `npm start`. Make sure you've added your Sendgrid API token. 169 | 170 | ## License 171 | 172 | Copyright (c) 2016 173 | 174 | Licensed under the [MIT license](LICENSE). 175 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | const feathers = require('feathers'); 2 | const rest = require('feathers-rest'); 3 | const hooks = require('feathers-hooks'); 4 | const socketio = require('feathers-socketio'); 5 | const bodyParser = require('body-parser'); 6 | const errorHandler = require('feathers-errors/handler'); 7 | const MailService = require('../lib').MailService; 8 | const mailerHooks = require('../lib').hooks; 9 | 10 | // Create a feathers instance. 11 | var app = feathers() 12 | // Enable REST services 13 | .configure(rest()) 14 | // Enable hooks 15 | .configure(hooks()) 16 | // Enable Socket.io services 17 | .configure(socketio()) 18 | // Turn on JSON parser for REST services 19 | .use(bodyParser.json()) 20 | // Turn on URL-encoded parser for REST services 21 | .use(bodyParser.urlencoded({extended: true})); 22 | 23 | app.use('/mailer', MailService({ apiKey: 'API_KEY' })); 24 | 25 | app.service('mailer').before({ 26 | create: [mailerHooks.validateEmail(), mailerHooks.normalizeEmail()] 27 | }); 28 | 29 | // Send an email! 30 | app.service('mailer').create({ 31 | from: 'hello@feathersjs.com', 32 | to: 'example@sendgrid.com', 33 | subject: 'Sendgrid test', 34 | content: 'Email body' 35 | }).then(function (result) { 36 | console.log('Sent email', result); 37 | }).catch(error => { 38 | console.log(error); 39 | }); 40 | 41 | app.use(errorHandler()); 42 | 43 | // Start the server. 44 | const port = 3030; 45 | 46 | app.listen(port, function () { 47 | console.log(`Feathers server listening on port ${port}`); 48 | }); 49 | -------------------------------------------------------------------------------- /mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive test/ 2 | --compilers js:babel-core/register -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-sendgrid", 3 | "description": "Feathers Sendgrid Service", 4 | "version": "0.3.0", 5 | "homepage": "https://github.com/feathersjs/feathers-sendgrid", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/feathersjs/feathers-sendgrid.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feathersjs/feathers-sendgrid/issues" 12 | }, 13 | "license": "MIT", 14 | "keywords": [ 15 | "feathers", 16 | "feathers-plugin", 17 | "REST", 18 | "Socket.io", 19 | "realtime", 20 | "sendgrid", 21 | "service" 22 | ], 23 | "author": "Feathers (http://feathersjs.com)", 24 | "contributors": [ 25 | "Cory Smith (https://github.com/corymsmith)", 26 | "Marshall Thompson (https://github.com/marshallswain)", 27 | "Eric Kryski (http://erickryski.com)", 28 | "David Luecke (http://neyeon.com)" 29 | ], 30 | "main": "lib/", 31 | "scripts": { 32 | "prepublish": "npm run compile", 33 | "publish": "git push origin && git push origin --tags", 34 | "release:patch": "npm version patch && npm publish", 35 | "release:minor": "npm version minor && npm publish", 36 | "release:major": "npm version major && npm publish", 37 | "compile": "rimraf lib/ && babel -d lib/ src/", 38 | "watch": "babel --watch -d lib/ src/", 39 | "lint": "eslint-if-supported semistandard --fix", 40 | "mocha": "mocha --opts mocha.opts", 41 | "test": "rimraf db-data && npm run compile && npm run lint && npm run coverage && nsp check", 42 | "start": "node example/app", 43 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts" 44 | }, 45 | "semistandard": { 46 | "env": [ 47 | "mocha" 48 | ], 49 | "ignore": [ 50 | "/lib" 51 | ] 52 | }, 53 | "engines": { 54 | "node": ">= 4" 55 | }, 56 | "dependencies": { 57 | "babel-polyfill": "^6.3.14", 58 | "debug": "^2.2.0", 59 | "feathers-errors": "^2.4.0", 60 | "lodash.merge": "^4.4.0", 61 | "request": "^2.73.0" 62 | }, 63 | "devDependencies": { 64 | "async": "^2.3.0", 65 | "babel-cli": "^6.3.17", 66 | "babel-core": "^6.3.26", 67 | "babel-plugin-add-module-exports": "^0.2.1", 68 | "babel-preset-es2015": "^6.3.13", 69 | "body-parser": "^1.13.2", 70 | "chai": "^3.0.0", 71 | "eslint-if-supported": "^1.0.1", 72 | "feathers": "^2.0.0-pre.4", 73 | "feathers-hooks": "^1.5.4", 74 | "feathers-rest": "^1.1.1", 75 | "feathers-socketio": "^1.3.3", 76 | "istanbul": "^1.1.0-alpha.1", 77 | "mocha": "^3.2.0", 78 | "nsp": "^2.2.0", 79 | "rimraf": "^2.5.4", 80 | "semistandard": "^10.0.0", 81 | "sinon": "^2.1.0", 82 | "sinon-chai": "^2.8.0" 83 | }, 84 | "resolutions": { 85 | "debug": "2.6.3", 86 | "async": "2.3.0", 87 | "ms": "0.7.2", 88 | "assert-plus": "1.0.0", 89 | "glob": "7.1.1", 90 | "source-map": "0.5.6", 91 | "minimist": "1.2.0", 92 | "object-assign": "4.1.1", 93 | "user-home": "2.0.0", 94 | "isarray": "1.0.0", 95 | "nopt": "4.0.1", 96 | "semver": "5.3.0", 97 | "string-width": "2.0.0", 98 | "is-fullwidth-code-point": "2.0.0", 99 | "supports-color": "3.1.2", 100 | "jsesc": "1.3.0", 101 | "path-to-regexp": "1.7.0", 102 | "component-emitter": "1.2.1", 103 | "wordwrap": "1.0.0", 104 | "strip-bom": "3.0.0", 105 | "diff": "3.2.0", 106 | "estraverse": "4.2.0", 107 | "acorn": "5.0.3" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import renderTemplate from './render-template'; 2 | import normalizeEmail from './normalize-email'; 3 | import sendEmail from './send-email'; 4 | import validateEmail from './validate-email'; 5 | 6 | const hooks = { 7 | renderTemplate, 8 | normalizeEmail, 9 | sendEmail, 10 | validateEmail 11 | }; 12 | export default hooks; 13 | -------------------------------------------------------------------------------- /src/hooks/normalize-email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // import { mail as helper } from 'sendgrid'; 4 | import Debug from 'debug'; 5 | 6 | const debug = new Debug('feathers-sendgrid:normalize-email'); 7 | 8 | export default function normalizeEmail () { 9 | return function (hook) { 10 | debug(`Normalizing hook.data`, hook.data); 11 | 12 | let data = Object.assign({}, hook.data); 13 | let recipients = []; 14 | 15 | if (typeof data.to === 'string') { 16 | recipients = [{ email: data.to }]; 17 | } else if (Array.isArray(data.to)) { // Support batch sending 18 | recipients = data.to.map(recipient => { 19 | // Handle array of emails (ie. ['user1@domain.com', 'user2@domain.com']) 20 | return typeof recipient === 'string' ? { email: recipient } : recipient; 21 | }); 22 | } 23 | 24 | data.personalizations = data.personalizations || [{ to: recipients }]; 25 | delete data.to; 26 | 27 | if (typeof data.from === 'string') { 28 | data.from = { email: data.from }; 29 | } 30 | 31 | if (typeof data.reply_to === 'string') { 32 | data.reply_to = { email: data.reply_to }; 33 | } 34 | 35 | if (typeof data.content === 'string') { 36 | const type = data.type || 'text/html'; 37 | data.content = [{ type, value: data.content }]; 38 | delete data.type; 39 | } 40 | 41 | hook.data = Object.assign({}, data); 42 | 43 | return Promise.resolve(hook); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/hooks/render-template.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import Debug from 'debug'; 6 | 7 | const debug = new Debug('feathers-sendgrid:render-template'); 8 | const defaults = { 9 | type: 'text/html' 10 | }; 11 | 12 | export default function renderTemplate (options = {}) { 13 | options = Object.assign({}, defaults, options); 14 | 15 | if (!options.engine) { 16 | throw new Error(`You must provide a view engine instance that exposes the 'compile' method via 'options.engine'.`); 17 | } 18 | 19 | return function (hook) { 20 | hook.params.template = hook.params.template || {}; 21 | 22 | return new Promise((resolve, reject) => { 23 | let filepath; 24 | const templateName = hook.params.template.name; 25 | const templateData = hook.params.template.data || {}; 26 | const extension = options.extension || hook.app.get('view engine'); 27 | 28 | if (!templateName) { 29 | return reject(new Error(`You must provide a template name via 'hook.params.template.name'.`)); 30 | } 31 | 32 | // TODO (EK): Test the filename for the extension 33 | 34 | if (options.path) { 35 | filepath = path.join(options.path, `${templateName}.${extension}`); 36 | } else { 37 | filepath = path.join(hook.app.get('views'), 'emails', `${templateName}.${extension}`); 38 | } 39 | 40 | debug(`Reading file ${filepath}`); 41 | 42 | fs.readFile(filepath, function (error, file) { 43 | if (error) { 44 | return reject(error); 45 | } 46 | 47 | try { 48 | debug(`Rendering file ${filepath} with data`, templateData); 49 | 50 | // Compile the template 51 | const template = options.engine.compile(file.toString()); 52 | const html = template(templateData); 53 | 54 | hook.data.type = options.type; 55 | hook.data.content = html; 56 | 57 | return resolve(hook); 58 | } catch (error) { 59 | debug(`Error rendering email template ${filepath}`, error); 60 | return reject(error); 61 | } 62 | }); 63 | }); 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/hooks/send-email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import merge from 'lodash.merge'; 4 | import Debug from 'debug'; 5 | 6 | const debug = new Debug('feathers-sendgrid:send-email'); 7 | 8 | const defaults = { 9 | mailerEndpoint: '/mailer', 10 | emailField: 'email', 11 | template: {} 12 | }; 13 | 14 | module.exports = function sendEmail (options = {}) { 15 | return function (hook) { 16 | options = Object.assign({}, defaults, hook.app.get('mailer'), options); 17 | 18 | debug('sendEmail called with options', options); 19 | 20 | let template = merge(hook.params.template, options.template); 21 | 22 | if (hook.type === 'before') { 23 | template.data = Object.assign({}, hook.data, template.data); 24 | } else if (hook.type === 'after') { 25 | let result = hook.result; 26 | 27 | // Handle Mongoose Models 28 | if (hook.result.toObject) { 29 | result = hook.result.toObject(); 30 | } else if (hook.result.toJSON) { 31 | // Handle Sequelize Models 32 | result = hook.result.toJSON(); 33 | } 34 | 35 | template.data = Object.assign({}, result, template.data); 36 | } 37 | 38 | // Attempt to get the email field from our data 39 | const recipient = template.data[options.emailField]; 40 | const data = Object.assign({}, options); 41 | const params = Object.assign({}, hook.params, { template }); 42 | 43 | // If a recipient wasn't already provided and we found one 44 | // in our data then let's use that one. 45 | if (recipient && (!data.to || !data.personalizations)) { 46 | data.to = recipient; 47 | } 48 | 49 | // Clean up some of our keys that shouldn't be passed on. 50 | delete data.mailerEndpoint; 51 | delete data.emailField; 52 | delete data.template; 53 | 54 | debug(`Calling app.service(${options.mailerEndpoint}).create with data`, data); 55 | debug(`Calling app.service(${options.mailerEndpoint}).create with params`, params); 56 | 57 | return new Promise((resolve, reject) => { 58 | hook.app.service(options.mailerEndpoint) 59 | .create(data, params) 60 | .then(() => { 61 | // return the original hook 62 | resolve(hook); 63 | }) 64 | .catch(reject); 65 | }); 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /src/hooks/validate-email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import errors from 'feathers-errors'; 4 | import Debug from 'debug'; 5 | 6 | const debug = new Debug('feathers-sendgrid:validate-email'); 7 | const expectedFields = [ 8 | 'from', 9 | 'to', 10 | 'subject', 11 | 'content' 12 | ]; 13 | 14 | export default function validateEmail () { 15 | return function (hook) { 16 | debug(`Validating hook.data`, hook.data); 17 | 18 | let error; 19 | 20 | for (let key of expectedFields) { 21 | if (hook.data[key] === undefined) { 22 | // If the recipients were specified in the personalizations ignore this key 23 | if (key === 'to' && hook.data.personalizations) { 24 | continue; 25 | } 26 | 27 | if (!error) { 28 | error = new errors.BadRequest('Invalid email data', { errors: {} }); 29 | } 30 | 31 | error.errors[key] = `'${key}' must be specified in hook.data.`; 32 | } 33 | } 34 | 35 | if (error) { 36 | return Promise.reject(error); 37 | } 38 | 39 | return Promise.resolve(hook); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | if (!global._babelPolyfill) { require('babel-polyfill'); } 2 | 3 | import mail from './services/mail'; 4 | import h from './hooks'; 5 | 6 | export const hooks = h; 7 | 8 | export const MailService = mail; 9 | // TODO (EK): Add more services for the sendgrid API 10 | 11 | export default mail; 12 | -------------------------------------------------------------------------------- /src/services/mail.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | import request from 'request'; 3 | import Debug from 'debug'; 4 | 5 | const debug = new Debug('feathers-sendgrid:mail'); 6 | 7 | const SENDGRID_API = 'https://api.sendgrid.com/v3'; 8 | 9 | export class MailService { 10 | constructor (options = {}) { 11 | if (!options.apiKey) { 12 | throw new Error('Sendgrid `apiKey` needs to be provided'); 13 | } 14 | 15 | this.options = options; 16 | } 17 | 18 | create (data) { 19 | return new Promise((resolve, reject) => { 20 | const options = { 21 | url: `${SENDGRID_API}/mail/send`, 22 | method: 'POST', 23 | json: true, 24 | headers: { 25 | 'User-Agent': 'feathers-sendgrid', 26 | 'Authorization': `Bearer ${this.options.apiKey}`, 27 | 'Content-Type': 'application/json', 28 | 'Accept': 'application/json' 29 | }, 30 | body: data 31 | }; 32 | 33 | debug(`Sending request`, options); 34 | 35 | request(options, (error, response, body) => { 36 | if (error) { 37 | return reject(error); 38 | } 39 | 40 | error = errors[response.statusCode]; 41 | 42 | if (error) { 43 | /* eslint new-cap: 0 */ 44 | return reject(new error('Error sending email to Sendgrid', body)); 45 | } 46 | 47 | resolve({ sent: true }); 48 | }); 49 | }); 50 | } 51 | } 52 | 53 | export default function (options) { 54 | debug(`Configuring Sendgrid Mail service with options:`, options); 55 | 56 | return new MailService(options); 57 | } 58 | -------------------------------------------------------------------------------- /test/src/hooks/index.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import hooks from '../../../src/hooks'; 3 | 4 | describe('Sendgrid hooks', () => { 5 | it('is CommonJS compatible', () => { 6 | expect(typeof require('../../../lib/hooks')).to.equal('object'); 7 | }); 8 | 9 | it('is ES6 compatible', () => { 10 | expect(typeof hooks).to.equal('object'); 11 | }); 12 | 13 | it('exposes normalizeEmail hook', () => { 14 | expect(typeof hooks.normalizeEmail).to.equal('function'); 15 | }); 16 | 17 | it('exposes validateEmail hook', () => { 18 | expect(typeof hooks.validateEmail).to.equal('function'); 19 | }); 20 | 21 | it('exposes renderTemplate hook', () => { 22 | expect(typeof hooks.renderTemplate).to.equal('function'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/src/hooks/normalize-email.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { normalizeEmail } from '../../../src/hooks'; 3 | 4 | describe('normalizeEmail', () => { 5 | let hook; 6 | 7 | beforeEach(() => { 8 | hook = { data: {} }; 9 | }); 10 | 11 | describe('to field', () => { 12 | it('removes key from data', (done) => { 13 | hook.data.to = 'user@example.com'; 14 | 15 | normalizeEmail()(hook).then(hook => { 16 | expect(hook.data.to).to.equal(undefined); 17 | done(); 18 | }).catch(done); 19 | }); 20 | 21 | describe('when it is a string', () => { 22 | it('converts to list of personalizations', (done) => { 23 | hook.data.to = 'user@example.com'; 24 | 25 | normalizeEmail()(hook).then(hook => { 26 | expect(hook.data.personalizations[0].to[0]).to.deep.equal({email: 'user@example.com'}); 27 | done(); 28 | }).catch(done); 29 | }); 30 | }); 31 | 32 | describe('when it is an array of strings', () => { 33 | it('converts to list of personalizations', (done) => { 34 | hook.data.to = ['user1@example.com', 'user2@example.com']; 35 | 36 | normalizeEmail()(hook).then(hook => { 37 | expect(hook.data.personalizations[0].to[0]).to.deep.equal({email: 'user1@example.com'}); 38 | expect(hook.data.personalizations[0].to[1]).to.deep.equal({email: 'user2@example.com'}); 39 | done(); 40 | }).catch(done); 41 | }); 42 | }); 43 | 44 | describe('when it is an array of objects', () => { 45 | it('converts to list of personalizations', (done) => { 46 | hook.data.to = [ 47 | {email: 'user1@example.com', name: 'user1'}, 48 | {email: 'user2@example.com', name: 'user2'} 49 | ]; 50 | 51 | normalizeEmail()(hook).then(hook => { 52 | expect(hook.data.personalizations[0].to[0]).to.deep.equal({email: 'user1@example.com', name: 'user1'}); 53 | expect(hook.data.personalizations[0].to[1]).to.deep.equal({email: 'user2@example.com', name: 'user2'}); 54 | done(); 55 | }).catch(done); 56 | }); 57 | }); 58 | }); 59 | 60 | describe('from field', () => { 61 | describe('when it is a string', () => { 62 | it('converts to an object', (done) => { 63 | hook.data.from = 'user@example.com'; 64 | 65 | normalizeEmail()(hook).then(hook => { 66 | expect(hook.data.from).to.deep.equal({ email: 'user@example.com' }); 67 | done(); 68 | }).catch(done); 69 | }); 70 | }); 71 | 72 | describe('when it is an object', () => { 73 | it('returns an object', (done) => { 74 | hook.data.from = { email: 'user@example.com', name: 'user' }; 75 | 76 | normalizeEmail()(hook).then(hook => { 77 | expect(hook.data.from).to.deep.equal({ email: 'user@example.com', name: 'user' }); 78 | done(); 79 | }).catch(done); 80 | }); 81 | }); 82 | }); 83 | 84 | describe('reply_to field', () => { 85 | describe('when it is a string', () => { 86 | it('converts to an object', (done) => { 87 | hook.data.reply_to = 'user@example.com'; 88 | 89 | normalizeEmail()(hook).then(hook => { 90 | expect(hook.data.reply_to).to.deep.equal({ email: 'user@example.com' }); 91 | done(); 92 | }).catch(done); 93 | }); 94 | }); 95 | 96 | describe('when it is an object', () => { 97 | it('returns an object', (done) => { 98 | hook.data.reply_to = { email: 'user@example.com', name: 'user' }; 99 | 100 | normalizeEmail()(hook).then(hook => { 101 | expect(hook.data.reply_to).to.deep.equal({ email: 'user@example.com', name: 'user' }); 102 | done(); 103 | }).catch(done); 104 | }); 105 | }); 106 | }); 107 | 108 | describe('content field', () => { 109 | describe('when it is a string', () => { 110 | it('converts to an array with an object', (done) => { 111 | hook.data.content = 'hello'; 112 | 113 | normalizeEmail()(hook).then(hook => { 114 | expect(hook.data.content[0]).to.deep.equal({ type: 'text/html', value: 'hello' }); 115 | done(); 116 | }).catch(done); 117 | }); 118 | }); 119 | }); 120 | 121 | describe('when personalizations are present', () => { 122 | it('returns the original', (done) => { 123 | const expected = [ 124 | { 125 | to: [{ email: 'user@example.com' }], 126 | cc: [{ email: 'me@example.com' }] 127 | } 128 | ]; 129 | 130 | hook.data.personalizations = expected; 131 | 132 | normalizeEmail()(hook).then(hook => { 133 | expect(hook.data.personalizations).to.deep.equal(expected); 134 | done(); 135 | }).catch(done); 136 | }); 137 | }); 138 | 139 | describe('when additional fields are present', () => { 140 | it('returns them', (done) => { 141 | hook.data.subject = 'Hello'; 142 | 143 | normalizeEmail()(hook).then(hook => { 144 | expect(hook.data.subject).to.equal('Hello'); 145 | done(); 146 | }).catch(done); 147 | }); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /test/src/hooks/render-template.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { renderTemplate } from '../../../src/hooks'; 3 | 4 | describe('renderTemplate', () => { 5 | let hook; // eslint-disable-line no-unused-vars 6 | 7 | beforeEach(() => { 8 | hook = { data: {} }; 9 | }); 10 | 11 | describe('with invalid options', () => { 12 | it('throws an error when engine is missing', () => { 13 | try { 14 | renderTemplate({template: 'welcome'}); 15 | } catch (error) { 16 | expect(error).to.not.equal(undefined); 17 | } 18 | }); 19 | }); 20 | 21 | describe('with valid options', () => { 22 | it.skip('returns an error when template is not provided', () => { 23 | 24 | }); 25 | 26 | it.skip('returns an error when template path not found', () => { 27 | 28 | }); 29 | 30 | it.skip('renders template', () => { 31 | 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/src/hooks/validate-email.test.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {validateEmail} from '../../../src/hooks'; 3 | 4 | describe('validateEmail', () => { 5 | let hook; 6 | 7 | beforeEach(() => { 8 | hook = {data: {}}; 9 | }); 10 | 11 | describe('when fields are missing', () => { 12 | it('returns a BadRequest error', (done) => { 13 | validateEmail()(hook).then(hook => { 14 | // should never get here 15 | expect(hook).to.equal(undefined); 16 | done(); 17 | }) 18 | .catch(error => { 19 | expect(error.code).to.equal(400); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('highlights missing key in errors object', (done) => { 25 | validateEmail()(hook).then(hook => { 26 | // should never get here 27 | expect(hook).to.equal(undefined); 28 | done(); 29 | }) 30 | .catch(error => { 31 | expect(error.errors.from).to.not.equal(undefined); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('when all fields are present', () => { 38 | it('returns original hook', (done) => { 39 | hook.data = { 40 | to: 'user@example.com', 41 | from: 'me@example.com', 42 | subject: 'Hi', 43 | content: 'Hello' 44 | }; 45 | 46 | validateEmail()(hook).then(hook => { 47 | expect(hook).to.deep.equal(hook); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('when to is missing but personalizations are present', () => { 54 | it('returns original hook', (done) => { 55 | hook.data = { 56 | personalizations: [{to: []}], 57 | from: 'me@example.com', 58 | subject: 'Hi', 59 | content: 'Hello' 60 | }; 61 | 62 | validateEmail()(hook).then(hook => { 63 | expect(hook).to.deep.equal(hook); 64 | done(); 65 | }).catch(done); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/src/index.test.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import sendgrid, {hooks, MailService} from '../../src'; 3 | 4 | describe('Feathers Sendgrid', () => { 5 | it('is CommonJS compatible', () => { 6 | expect(typeof require('../../lib')).to.equal('object'); 7 | }); 8 | 9 | it('is ES6 compatible', () => { 10 | expect(typeof sendgrid).to.equal('function'); 11 | }); 12 | 13 | it('exposes hooks', () => { 14 | expect(typeof hooks).to.equal('object'); 15 | }); 16 | 17 | it('exposes MailService', () => { 18 | expect(typeof MailService).to.equal('function'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/src/services/mail.test.js: -------------------------------------------------------------------------------- 1 | // import chai from 'chai'; 2 | // import { expect } from 'chai'; 3 | // import assert from 'assert'; 4 | // import sinon from 'sinon'; 5 | // import sinonChai from 'sinon-chai'; 6 | 7 | // chai.use(sinonChai); 8 | 9 | // import feathers from 'feathers'; 10 | 11 | // import server from './test-app'; 12 | // import service from '../src'; 13 | 14 | // import Sendgrid from 'sendgrid'; 15 | // const sendgrid = new Sendgrid('API_KEY'); 16 | 17 | // const sendgridService = service({apiKey: 'API_KEY'}); 18 | // const app = feathers().use('/mailer', sendgridService); 19 | 20 | // const validParams = { 21 | // from: 'from@from.com', 22 | // to: 'to@to.com', 23 | // subject: 'Email Subject', 24 | // html: 'message html' 25 | // }; 26 | 27 | // const validParamsWithArrayInToField = { 28 | // from: 'from@from.com', 29 | // to: ['to@to.com', 'to2@to.com'], 30 | // subject: 'Email Subject', 31 | // html: 'message html' 32 | // }; 33 | 34 | // describe('Sendgrid Service', function () { 35 | 36 | // after(done => server.close(() => done())); 37 | 38 | // describe('Initialization', () => { 39 | 40 | // describe('without an api key', () => { 41 | // it('throws an error', () => { 42 | // expect(service.bind(null, {})).to.throw('Sendgrid `apiKey` needs to be provided'); 43 | // }); 44 | // }); 45 | // }); 46 | 47 | // describe('Validation', () => { 48 | 49 | // describe('when missing `from` field', () => { 50 | // it('throws an error', (done) => { 51 | // app.service('mailer').create({}).then(done).catch(err => { 52 | // assert.equal(err.code, 400); 53 | // assert.equal(err.message, '`from` must be specified'); 54 | // done(); 55 | // }); 56 | // }); 57 | // }); 58 | 59 | // describe('when missing `to` field', () => { 60 | // it('throws an error', (done) => { 61 | // app.service('mailer').create({from: 'from@from.com'}).then(done).catch(err => { 62 | // assert.equal(err.code, 400); 63 | // assert.equal(err.message, '`to` must be specified'); 64 | // done(); 65 | // }); 66 | // }); 67 | // }); 68 | 69 | // describe('when missing `subject` field', () => { 70 | // it('throws an error', (done) => { 71 | // app.service('mailer').create({from: 'from@from.com', to: 'to@to.com'}).then(done).catch(err => { 72 | // assert.equal(err.code, 400); 73 | // assert.equal(err.message, '`subject` must be specified'); 74 | // done(); 75 | // }); 76 | // }); 77 | // }); 78 | 79 | // describe('when missing `html` field', () => { 80 | // it('throws an error', (done) => { 81 | // app.service('mailer').create({ 82 | // from: 'from@from.com', 83 | // to: 'to@to.com', 84 | // subject: 'Email Subject' 85 | // }).then(done).catch(err => { 86 | // assert.equal(err.code, 400); 87 | // assert.equal(err.message, '`html` or `text` must be specified'); 88 | // done(); 89 | // }); 90 | // }); 91 | // }); 92 | // }); 93 | 94 | // describe('Sending messages', () => { 95 | 96 | // var sendgridSend; 97 | // beforeEach(function (done) { 98 | // sendgridSend = 99 | // sinon 100 | // .stub(app.service('mailer'), '_send', function (data, callback) { 101 | // callback(null, {success: true}); 102 | // }); 103 | // done(); 104 | // }); 105 | 106 | // afterEach(function (done) { 107 | // sendgridSend.restore(); 108 | // done(); 109 | // }); 110 | 111 | // describe('when sending to an array of email addresses', () => { 112 | 113 | // it('correctly adds them to email', (done) => { 114 | 115 | // var email = new sendgrid.Email({from: validParamsWithArrayInToField.from, html: validParamsWithArrayInToField.html, subject: validParamsWithArrayInToField.subject}); 116 | // email.addSmtpapiTo('to@to.com'); 117 | // email.addSmtpapiTo('to2@to.com'); 118 | 119 | // app.service('mailer').create(validParamsWithArrayInToField).then(result => { 120 | // expect(result.success).to.eql(true); 121 | // expect(sendgridSend).to.have.been.calledWith(email); 122 | // done(); 123 | // }); 124 | // }); 125 | // }); 126 | 127 | // describe('when all fields are valid', () => { 128 | // it('successfully sends a message', (done) => { 129 | // app.service('mailer').create(validParams).then(result => { 130 | // expect(result.success).to.eql(true); 131 | // var email = new sendgrid.Email(validParams); 132 | // expect(sendgridSend).to.have.been.calledWith(email); 133 | // done(); 134 | // }); 135 | // }); 136 | // }); 137 | 138 | // }); 139 | 140 | // describe('Common functionality', () => { 141 | // it('is CommonJS compatible', () => { 142 | // assert.ok(typeof require('../lib') === 'function'); 143 | // }); 144 | // }); 145 | 146 | // }); 147 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | const feathers = require('feathers'); 2 | const mailService = require('../lib').MailService; 3 | 4 | // Create the sendgrid service 5 | const sendgridService = mailService({ 6 | apiKey: 'API_KEY' 7 | }); 8 | 9 | // Create a feathers instance with a mailer service 10 | var app = feathers() 11 | .use('/mailer', sendgridService); 12 | 13 | // Start the server. 14 | const port = 3030; 15 | 16 | module.exports = app.listen(port); 17 | --------------------------------------------------------------------------------