├── docs
├── .nojekyll
├── debug.md
├── api
│ ├── events.md
│ ├── stats.md
│ ├── bounces.md
│ ├── complaints.md
│ ├── message.md
│ ├── unsubscribes.md
│ ├── routes.md
│ ├── tracking.md
│ ├── credentials.md
│ ├── list.md
│ ├── domain.md
│ ├── members.md
│ └── tags.md
├── promises.md
├── tests.md
├── _sidebar.md
├── generic.md
├── webhook.md
├── email_validation.md
├── index.html
├── batch.md
├── testmode.md
├── README.md
└── attach.md
├── test
├── data
│ ├── mailgun_logo.png
│ ├── message.eml
│ └── fixture.json
├── constructor.test.js
├── stats.test.js
├── events.test.js
├── genericrequests.test.js
├── unsubscribes.test.js
├── complaints.test.js
├── bounces.test.js
├── tags.test.js
├── routes.test.js
├── validation.test.js
├── sendmime.test.js
├── testmode.test.js
├── domains.test.js
├── mailinglists.test.js
└── messages.test.js
├── .gitignore
├── .npmignore
├── ISSUE_TEMPLATE.md
├── README.md
├── LICENSE.txt
├── lib
├── attachment.js
├── build.js
├── mailgun.js
├── request.js
└── schema.js
├── package.json
└── bin
└── docs
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/data/mailgun_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mailgun/mailgun-js-boland/HEAD/test/data/mailgun_logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.orig
2 | .idea
3 | .DS_Store
4 | node_modules
5 | dump.rdb
6 | npm-debug.log
7 | main.js
8 | /test/data/auth.json
9 | _book
10 |
--------------------------------------------------------------------------------
/docs/debug.md:
--------------------------------------------------------------------------------
1 | ## Debug logging
2 |
3 | [debug](https://npmjs.com/package/debug) package is used for debug logging.
4 |
5 | ```sh
6 | DEBUG=mailgun-js node app.js
7 | ```
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # From .gitignore
2 | /npm-debug.log
3 | *.orig
4 | .idea
5 | dump.rdb
6 | main.js
7 | _book
8 |
9 | # Additions for .npmignore only
10 | test
11 | /docs/
12 | /bin/
13 | /.npmignore
14 |
--------------------------------------------------------------------------------
/docs/api/events.md:
--------------------------------------------------------------------------------
1 | # events
2 |
3 | Query events that happen to your emails. See http://documentation.mailgun.com/api-events.html
4 |
5 | ## Actions
6 |
7 | ### `get`
8 |
9 | Queries event records.
10 |
11 | `mailgun.events().get({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/events
16 |
17 |
--------------------------------------------------------------------------------
/docs/api/stats.md:
--------------------------------------------------------------------------------
1 | # stats
2 |
3 | Various data and event statistics for you mailgun account. See http://documentation.mailgun.com/api-stats.html
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Returns a list of event stat items. Each record represents counts for one event per one day.
10 |
11 | `mailgun.stats().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/stats
16 |
17 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 1) What version of the module is the issue happening on? Does the issue happen on latest version?
2 |
3 | 2) What platform and Node.js version? (For example Node.js 6.9.1 on Mac OS X)
4 |
5 | 3) Does the action work when you manually perform request against mailgun using curl (or other means)?
6 |
7 | 4) Sample source code or steps to reproduce
8 |
9 | (Write description of your issue here, stack traces from errors and code that reproduces the issue are helpful)
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mailgun.js
2 |
3 | Simple Node.js module for [Mailgun](http://www.mailgun.com).
4 |
5 | # Maintenance Notice
6 |
7 | This library is no longer maintained. Mailgun now has a supported, official library at https://github.com/mailgun/mailgun-js. It is also available as an npm at https://www.npmjs.com/package/mailgun.js. I'd encourage everyone to migrate from this library to the official library. If you have questions or issues with the official library, please raise issues there.
8 |
--------------------------------------------------------------------------------
/test/constructor.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Constructor', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('instance constructor', () => {
15 | const mg = new mailgun.Mailgun({
16 | 'apiKey': auth.api_key,
17 | 'domain': auth.domain
18 | })
19 |
20 | assert.ok(mg)
21 | assert.ok(mg instanceof mailgun.Mailgun)
22 | assert.ok(mg instanceof mg.Mailgun)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/docs/promises.md:
--------------------------------------------------------------------------------
1 | ## Promises
2 |
3 | Module works with Node-style callbacks, but also implements (native) Promise based API. When a callback is not passed into to a function a Promise is returned that is resolved or rejected when the action succeeds to fails.
4 |
5 | ```js
6 | mailgun.lists('mylist@mydomain.com').info().then(function (data) {
7 | console.log(data)
8 | }, function (err) {
9 | console.log(err)
10 | })
11 | ```
12 |
13 | With `async` / `await`:
14 |
15 | ```js
16 | async function main () {
17 | const data = await mailgun.lists('mylist@mydomain.com').info()
18 | console.log(data)
19 | }
20 |
21 | main()
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/tests.md:
--------------------------------------------------------------------------------
1 | ## Tests
2 |
3 | To run the test suite you must first have a Mailgun account with a domain setup. Then create a file named _./test/data/auth.json_, which contains your credentials as JSON, for example:
4 |
5 | ```json
6 | { "api_key": "XXXXXXXXXXXXXXXXXXXXXXX", "public_api_key": "XXXXXXXXXXXXXXXXXXXXXXX", "domain": "mydomain.mailgun.org" }
7 | ```
8 |
9 | You should edit _./test/data/fixture.json_ and modify the data to match your context.
10 |
11 | Then install the dev dependencies and execute the test suite:
12 |
13 | ```
14 | $ npm install
15 | $ npm test
16 | ```
17 |
18 | The tests will call Mailgun API, and will send a test email, create route(s), mailing list and mailing list member.
19 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - Guide
2 | - [Introduction](/README)
3 | - [Attachments](/attach)
4 | - [Batch Sending](/batch)
5 | - [Generic Requests](/generic)
6 | - [Promises](/promises)
7 | - [Webhook Validation](/webhook)
8 | - [Email Validation](/email_validation)
9 | - [Debug Logging](/debug)
10 | - [Test Mode](/testmode)
11 | - [Tests](/tests)
12 | - API Reference
13 | - [Message](/api/message)
14 | - [Domain](/api/domain)
15 | - [Credentials](/api/credentials)
16 | - [Tracking](/api/tracking)
17 | - [Complaints](/api/complaints)
18 | - [Events](/api/events)
19 | - [Stats](/api/stats)
20 | - [Tags](/api/tags)
21 | - [Bounces](/api/bounces)
22 | - [Unsubscribes](/api/unsubscribes)
23 | - [Routes](/api/routes)
24 | - [Mailing Lists](/api/list)
25 | - [Mailing List Members](/api/members)
26 |
--------------------------------------------------------------------------------
/docs/generic.md:
--------------------------------------------------------------------------------
1 | ## Generic requests
2 |
3 | Mailgun-js also provides helper methods to allow users to interact with parts of the api that are not exposed already.
4 | These are not tied to the domain passed in the constructor, and thus require the full path with the domain
5 | passed in the `resource` argument.
6 |
7 | * `mailgun.get(resource, data, callback)` - sends GET request to the specified resource on api.
8 | * `mailgun.post(resource, data, callback)` - sends POST request to the specified resource on api.
9 | * `mailgun.delete(resource, data, callback)` - sends DELETE request to the specified resource on api.
10 | * `mailgun.put(resource, data, callback)` - sends PUT request to the specified resource on api.
11 |
12 | Example: Get some stats
13 |
14 | ```js
15 | mailgun.get('/samples.mailgun.org/stats', { event: ['sent', 'delivered'] }, function (error, body) {
16 | console.log(body);
17 | });
18 | ```
19 |
--------------------------------------------------------------------------------
/test/data/message.eml:
--------------------------------------------------------------------------------
1 | To: mm@samples.mailgun.org
2 | Subject: Test email subject
3 | Content-Type: multipart/alternative;
4 | boundary="----mailcomposer-?=_1-1410115176966"
5 | MIME-Version: 1.0
6 |
7 | ------mailcomposer-?=_1-1410115176966
8 | Content-Type: text/plain; charset=utf-8
9 | Content-Transfer-Encoding: quoted-printable
10 |
11 | Test email text
12 | To: mm@samples.mailgun.org
13 | To: mm@samples.mailgun.org
14 | Subject: Test email subject
15 | Subject: Test email subject
16 | Content-Type: multipart/alternative;
17 | boundary="----mailcomposer-?=_1-1410115176966"
18 | Content-Type: multipart/alternative;
19 | boundary="----mailcomposer-?=_2-1410115176970"
20 | MIME-Version: 1.0
21 |
22 | ------mailcomposer-?=_2-1410115176970
23 | Content-Type: text/html; charset=utf-8
24 | Content-Transfer-Encoding: quoted-printable
25 |
26 | Test email text
27 | ------mailcomposer-?=_2-1410115176970--
28 |
--------------------------------------------------------------------------------
/docs/api/bounces.md:
--------------------------------------------------------------------------------
1 | # bounces
2 |
3 | Mailgun automatically handles bounced emails. The list of bounced addresses can be accessed programmatically.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Fetches the list of bounces.
10 |
11 | `mailgun.bounces().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/bounces
16 |
17 | ### `info`
18 |
19 | Fetches a single bounce event by a given email address.
20 |
21 | `mailgun.bounces({address}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /{domain}/bounces/{address}
26 |
27 | ### `delete`
28 |
29 | Clears a given bounce event.
30 |
31 | `mailgun.bounces({address}).delete({callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | DELETE | /{domain}/bounces/{address}
36 |
37 | ### `create`
38 |
39 | Adds a permanent bounce to the bounces table. Updates the existing record if already here.
40 |
41 | `mailgun.bounces().create({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | POST | /{domain}/bounces
46 |
47 |
--------------------------------------------------------------------------------
/docs/api/complaints.md:
--------------------------------------------------------------------------------
1 | # complaints
2 |
3 | This API allows you to programmatically download the list of users who have complained, add a complaint, or delete a complaint.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Fetches the list of complaints.
10 |
11 | `mailgun.complaints().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/complaints
16 |
17 | ### `create`
18 |
19 | Adds an address to the complaints table.
20 |
21 | `mailgun.complaints().create({attributes}, {callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | POST | /{domain}/complaints
26 |
27 | ### `info`
28 |
29 | Fetches a single spam complaint by a given email address.
30 |
31 | `mailgun.complaints({address}).info({callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | GET | /{domain}/complaints/{address}
36 |
37 | ### `delete`
38 |
39 | Removes a given spam complaint.
40 |
41 | `mailgun.complaints({address}).delete({callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | DELETE | /{domain}/complaints/{address}
46 |
47 |
--------------------------------------------------------------------------------
/docs/webhook.md:
--------------------------------------------------------------------------------
1 | ## Webhook validation
2 |
3 | The Mailgun object also has a helper function for validating Mailgun Webhook requests
4 | (as per the [mailgun docs for securing webhooks](http://documentation.mailgun.com/user_manual.html#securing-webhooks)).
5 | This code came from [this gist](https://gist.github.com/coolaj86/81a3b61353d2f0a2552c).
6 |
7 | Example usage:
8 |
9 | ```js
10 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
11 |
12 | function router(app) {
13 | app.post('/webhooks/mailgun/*', function (req, res, next) {
14 | var body = req.body;
15 |
16 | if (!mailgun.validateWebhook(body.timestamp, body.token, body.signature)) {
17 | console.error('Request came, but not from Mailgun');
18 | res.send({ error: { message: 'Invalid signature. Are you even Mailgun?' } });
19 | return;
20 | }
21 |
22 | next();
23 | });
24 |
25 | app.post('/webhooks/mailgun/catchall', function (req, res) {
26 | // actually handle request here
27 | });
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/api/message.md:
--------------------------------------------------------------------------------
1 | # message
2 |
3 | This API allows you to send, access, and delete mesages programmatically.
4 |
5 | ## Actions
6 |
7 | ### `info`
8 |
9 | Returns a single message in JSON format. To get full MIME message set MIME to true
10 |
11 | `mailgun.messages({message}).info({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /domains/{domain}/messages/{message}
16 |
17 | ### `send`
18 |
19 | Sends a message by assembling it from the components.
20 |
21 | `mailgun.messages().send({attributes}, {callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | POST | /{domain}/messages
26 |
27 | ### `sendMime`
28 |
29 | Sends a message in MIME format.
30 |
31 | `mailgun.messages().sendMime({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | POST | /{domain}/messages.mime
36 |
37 | ### `delete`
38 |
39 | To delete an inbound message that has been stored via the store() action.
40 |
41 | `mailgun.messages({message}).delete({callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | DELETE | /domains/{domain}/messages/{message}
46 |
47 |
--------------------------------------------------------------------------------
/docs/api/unsubscribes.md:
--------------------------------------------------------------------------------
1 | # unsubscribes
2 |
3 | This API allows you to programmatically download the list of recipients who have unsubscribed from your emails. You can also programmatically “clear” the unsubscribe event.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Fetches the list of unsubscribes.
10 |
11 | `mailgun.unsubscribes().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/unsubscribes
16 |
17 | ### `info`
18 |
19 | Retreives a single unsubscribe record.
20 |
21 | `mailgun.unsubscribes({address}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /{domain}/unsubscribes/{address}
26 |
27 | ### `delete`
28 |
29 | Removes an address from the unsubscribes table.
30 |
31 | `mailgun.unsubscribes({address}).delete({callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | DELETE | /{domain}/unsubscribes/{address}
36 |
37 | ### `create`
38 |
39 | Adds address to unsubscribed table.
40 |
41 | `mailgun.unsubscribes().create({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | POST | /{domain}/unsubscribes
46 |
47 |
--------------------------------------------------------------------------------
/docs/api/routes.md:
--------------------------------------------------------------------------------
1 | # routes
2 |
3 | Mailgun Routes are a powerful way to handle the incoming traffic. This API allows you to work with routes programmatically.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Fetches the list of routes.
10 |
11 | `mailgun.routes().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /routes
16 |
17 | ### `info`
18 |
19 | Returns a single route object based on its ID.
20 |
21 | `mailgun.routes({id}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /routes/{id}
26 |
27 | ### `create`
28 |
29 | Creates a new route.
30 |
31 | `mailgun.routes().create({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | POST | /routes
36 |
37 | ### `update`
38 |
39 | Updates a given route by ID.
40 |
41 | `mailgun.routes({id}).update({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | PUT | /routes/{id}
46 |
47 | ### `delete`
48 |
49 | Deletes a route based on the id.
50 |
51 | `mailgun.routes({id}).delete({callback});`
52 |
53 | Method | Path
54 | --- | ---
55 | DELETE | /routes/{id}
56 |
57 |
--------------------------------------------------------------------------------
/docs/api/tracking.md:
--------------------------------------------------------------------------------
1 | # tracking
2 |
3 | Programmatically get and modify domain tracking settings.
4 |
5 | ## Actions
6 |
7 | ### `info`
8 |
9 | Returns tracking settings for a domain.
10 |
11 | `mailgun.domains({domain}).tracking().info({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /domains/{domain}/tracking
16 |
17 | ### `update`
18 |
19 | Updates the open tracking settings for a domain.
20 |
21 | `mailgun.domains({domain}).tracking().open().update({attributes}, {callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | PUT | /domains/{domain}/tracking/open
26 |
27 | ### `update`
28 |
29 | Updates the click tracking settings for a domain.
30 |
31 | `mailgun.domains({domain}).tracking().click().update({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | PUT | /domains/{domain}/tracking/click
36 |
37 | ### `update`
38 |
39 | Updates the unsubscribe tracking settings for a domain.
40 |
41 | `mailgun.domains({domain}).tracking().unsubscribe().update({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | PUT | /domains/{domain}/tracking/unsubscribe
46 |
47 |
--------------------------------------------------------------------------------
/docs/api/credentials.md:
--------------------------------------------------------------------------------
1 | # credentials
2 |
3 | Programmatically get and modify domain credentials.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Returns a list of SMTP credentials for the defined domain.
10 |
11 | `mailgun.domains({domain}).credentials().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /domains/{domain}/credentials
16 |
17 | ### `create`
18 |
19 | Creates a new set of SMTP credentials for the defined domain.
20 |
21 | `mailgun.domains({domain}).credentials().create({attributes}, {callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | POST | /domains/{domain}/credentials
26 |
27 | ### `update`
28 |
29 | Updates the specified SMTP credentials. Currently only the password can be changed.
30 |
31 | `mailgun.domains({domain}).credentials({login}).update({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | PUT | /domains/{domain}/credentials/{login}
36 |
37 | ### `delete`
38 |
39 | Deletes the defined SMTP credentials.
40 |
41 | `mailgun.domains({domain}).credentials({login}).delete({callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | DELETE | /domains/{domain}/credentials/{login}
46 |
47 |
--------------------------------------------------------------------------------
/docs/api/list.md:
--------------------------------------------------------------------------------
1 | # list
2 |
3 | You can programmatically work with mailing lists and mailing list members using Mailgun Mailing List API.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Returns a list of mailing lists under your account.
10 |
11 | `mailgun.lists().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /lists
16 |
17 | ### `info`
18 |
19 | Returns a single mailing list by a given address.
20 |
21 | `mailgun.lists({address}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /lists/{address}
26 |
27 | ### `create`
28 |
29 | Creates a new mailing list.
30 |
31 | `mailgun.lists().create({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | POST | /lists
36 |
37 | ### `update`
38 |
39 | Update mailing list properties, such as address, description or name.
40 |
41 | `mailgun.lists({address}).update({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | PUT | /lists/{address}
46 |
47 | ### `delete`
48 |
49 | Deletes a mailing list.
50 |
51 | `mailgun.lists({address}).delete({callback});`
52 |
53 | Method | Path
54 | --- | ---
55 | DELETE | /lists/{address}
56 |
57 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2012 - 2017 OneLobby and Bojan D.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/stats.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Stats', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('test mailgun.stats().list()', (done) => {
15 | mailgun.stats().list((err, body) => {
16 | assert.ifError(err)
17 | assert.ok(body.total_count)
18 | assert.ok(body.items)
19 | done()
20 | })
21 | })
22 |
23 | it('test mailgun.stats().list() with one argument', (done) => {
24 | mailgun.stats().list({
25 | 'event': 'delivered'
26 | }, (err, body) => {
27 | assert.ifError(err)
28 | assert.ok(body.total_count)
29 | assert.ok(body.items)
30 | done()
31 | })
32 | })
33 |
34 | it('test mailgun.stats().list() with arguments', (done) => {
35 | mailgun.stats().list({
36 | 'event': ['sent', 'delivered']
37 | }, (err, body) => {
38 | assert.ifError(err)
39 | assert.ok(body.total_count)
40 | assert.ok(body.items)
41 | done()
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/docs/api/domain.md:
--------------------------------------------------------------------------------
1 | # domain
2 |
3 | This API allows you to create, access, and validate domains programmatically.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Returns a list of domains under your account in JSON.
10 |
11 | `mailgun.domains().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /domains
16 |
17 | ### `info`
18 |
19 | Returns a single domain, including credentials and DNS records.
20 |
21 | `mailgun.domains({domain}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /domains/{domain}
26 |
27 | ### `create`
28 |
29 | Create a new domain.
30 |
31 | `mailgun.domains().create({attributes}, {callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | POST | /domains
36 |
37 | ### `delete`
38 |
39 | Delete a domain from your account.
40 |
41 | `mailgun.domains({domain}).delete({callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | DELETE | /domains/{domain}
46 |
47 | ### `verify`
48 |
49 | Verifies and returns a single domain, including credentials and DNS records.
50 |
51 | `mailgun.domains({domain}).verify().verify({attributes}, {callback});`
52 |
53 | Method | Path
54 | --- | ---
55 | PUT | /domains/{domain}/verify
56 |
57 |
--------------------------------------------------------------------------------
/test/events.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Events', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('test mailgun.events().get() simple recipient query', (done) => {
15 | const query = {
16 | 'begin': 'Fri, 3 May 2013 09:00:00 -0000',
17 | 'ascending': 'yes',
18 | 'limit': 25,
19 | 'pretty': 'yes',
20 | 'recipient': 'mm@samples.mailgun.org'
21 | }
22 |
23 | mailgun.events().get(query, (err, body) => {
24 | assert.ifError(err)
25 | assert.ok(body.items)
26 | done()
27 | })
28 | })
29 |
30 | it('test mailgun.events().get() simple event query', (done) => {
31 | const query = {
32 | 'event': 'rejected OR failed'
33 | }
34 |
35 | mailgun.events().get(query, (err, body) => {
36 | assert.ifError(err)
37 | assert.ok(body.items)
38 | done()
39 | })
40 | })
41 |
42 | it('test mailgun.events().get() without any params', (done) => {
43 | mailgun.events().get((err, body) => {
44 | assert.ifError(err)
45 | assert.ok(body.items)
46 | done()
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/docs/email_validation.md:
--------------------------------------------------------------------------------
1 | ## Email Addresses validation
2 |
3 | Please check Mailgun [email validation documentation](https://documentation.mailgun.com/api-email-validation.html) for more responses details.
4 |
5 | ### Validate Email Address
6 |
7 | **mailgun.validate(address, private, options, fn)**
8 |
9 | Checks if email is valid.
10 |
11 | - `private` - wether it's private validate
12 | - `options` - any additional options
13 |
14 | Example usage:
15 |
16 | ```js
17 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
18 |
19 | mailgun.validate('test@mail.com', function (err, body) {
20 | if (body && body.is_valid) {
21 | // do something
22 | }
23 | });
24 | ```
25 |
26 | ### Parse Email Addresses list
27 |
28 | **mailgun.parse(address, private, options, fn)**
29 |
30 | - `private` - wether it's private validate
31 | - `options` - any additional options
32 |
33 | Parses list of email addresses and returns two lists:
34 | - parsed email addresses
35 | - unparseable email addresses
36 |
37 | Example usage:
38 |
39 | ```js
40 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
41 |
42 | mailgun.parse([ 'test@mail.com', 'test2@mail.com' ], function (err, body) {
43 | if (error) {
44 | // handle error
45 | } else {
46 | // do something with parsed addresses: body.parsed;
47 | // do something with unparseable addresses: body.unparseable;
48 | }
49 | });
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mailgun-js - Simple Node.js helper module for Mailgun API
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/genericrequests.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Generic requests functions', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('test mailgun.get()', (done) => {
15 | const path = `/${auth.domain}/stats`
16 |
17 | mailgun.get(path, {
18 | 'event': ['sent', 'delivered']
19 | }, (err, body) => {
20 | assert.ifError(err)
21 | assert.ok(body.total_count)
22 | assert.ok(body.items)
23 | done()
24 | })
25 | })
26 |
27 | it('test mailgun.get() using promises', (done) => {
28 | const path = `/${auth.domain}/stats`
29 |
30 | mailgun.get(path, {
31 | 'event': ['sent', 'delivered']
32 | }).then((body) => {
33 | assert.ok(body.total_count)
34 | assert.ok(body.items)
35 | done()
36 | }).catch((err) => {
37 | assert.ifError(err)
38 | done()
39 | })
40 | })
41 |
42 | it('test mailgun.get() using promises 2', (done) => {
43 | const mg = new mailgun.Mailgun({
44 | 'apiKey': auth.public_api_key,
45 | 'domain': auth.domain
46 | })
47 |
48 | mg.get('/address/validate', {
49 | 'address': 'emailtestmg@gmail.com'
50 | }).then(body => {
51 | assert.ok(body)
52 | done()
53 | }).catch(err => {
54 | assert.ifError(err)
55 | done()
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/test/unsubscribes.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | describe('Unsubscribes', () => {
11 | beforeEach((done) => {
12 | setTimeout(done, 500)
13 | })
14 |
15 | it('test unsubscribes().create()', (done) => {
16 | mailgun.unsubscribes().create(fixture.unsubscribe, (err, body) => {
17 | assert.ifError(err)
18 | assert.ok(body)
19 | done()
20 | })
21 | })
22 |
23 | it('test unsubscribes().create() multi', (done) => {
24 | mailgun.unsubscribes().create([fixture.unsubscribe, fixture.unsubscribe2], (err, body) => {
25 | assert.ifError(err)
26 | assert.ok(body)
27 | done()
28 | })
29 | })
30 |
31 | it('test unsubscribes().list()', (done) => {
32 | mailgun.unsubscribes().list((err, body) => {
33 | assert.ifError(err)
34 | assert.ok(body.items)
35 | done()
36 | })
37 | })
38 |
39 | it('test unsubscribes().info()', (done) => {
40 | mailgun.unsubscribes(fixture.unsubscribe.address).info((err, body) => {
41 | assert.ifError(err)
42 | assert.ok(body)
43 | done()
44 | })
45 | })
46 |
47 | it('test unsubscribes().delete()', (done) => {
48 | mailgun.unsubscribes(fixture.unsubscribe.address).delete((err, body) => {
49 | assert.ifError(err)
50 | assert.ok(body)
51 | done()
52 | })
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/test/complaints.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | describe('Complaints', () => {
11 | beforeEach((done) => {
12 | setTimeout(done, 500)
13 | })
14 |
15 | it('test complaints().create() missing address', (done) => {
16 | mailgun.complaints().create({}, (err) => {
17 | assert.ok(err)
18 | assert(/Missing parameter 'address'/.test(err.message))
19 | done()
20 | })
21 | })
22 |
23 | it('test complaints().create()', (done) => {
24 | mailgun.complaints().create({
25 | 'address': fixture.complaints.address
26 | }, (err, body) => {
27 | assert.ifError(err)
28 | assert.ok(body.message)
29 | done()
30 | })
31 | })
32 |
33 | it('test complaints().list()', (done) => {
34 | mailgun.complaints().list((err, body) => {
35 | assert.ifError(err)
36 | assert.ok(body.items)
37 | done()
38 | })
39 | })
40 |
41 | it('test complaints().info()', (done) => {
42 | mailgun.complaints(fixture.complaints.address).info((err, body) => {
43 | assert.ifError(err)
44 | assert.ok(body)
45 | done()
46 | })
47 | })
48 |
49 | it('test complaints().delete()', (done) => {
50 | mailgun.complaints(fixture.complaints.address).delete((err, body) => {
51 | assert.ifError(err)
52 | assert.ok(body.message)
53 | done()
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/lib/attachment.js:
--------------------------------------------------------------------------------
1 | const isStream = require('is-stream')
2 |
3 | /**
4 | * Creates an Attachment object.
5 | * @param {Object} options Buffer representing attachment data
6 | * data - one of:
7 | * - string representing the full file path
8 | * - buffer of the data
9 | * - readable interface (stream)
10 | * filename - optionally the filename to be used for the attachment, should be used if passing
11 | * buffer or stream in the data param
12 | * contentType - the content type for header info. Should be passed in if using stream for data
13 | * knownLength - the known length of the data. Should be passed in if using stream for data
14 | * @constructor
15 | */
16 | class Attachment {
17 | constructor (options) {
18 | const data = options.data
19 |
20 | if (data) {
21 | if (typeof data === 'string' || Buffer.isBuffer(data) || isStream(data)) {
22 | this.data = data
23 | }
24 | }
25 |
26 | this.filename = options.filename
27 | this.contentType = options.contentType
28 | this.knownLength = options.knownLength
29 | }
30 |
31 | getType () {
32 | if (this.data) {
33 | if (typeof this.data === 'string') {
34 | return 'path'
35 | } else if (Buffer.isBuffer(this.data)) {
36 | return 'buffer'
37 | } else if (isStream(this.data)) {
38 | return 'stream'
39 | }
40 | }
41 |
42 | return 'unknown'
43 | }
44 | }
45 |
46 | module.exports = Attachment
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mailgun-js",
3 | "description": "Simple Node.js helper module for Mailgun API",
4 | "keywords": [
5 | "email",
6 | "mailgun"
7 | ],
8 | "version": "0.23.0",
9 | "homepage": "https://github.com/bojand/mailgun-js",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/bojand/mailgun-js.git"
14 | },
15 | "bugs": {
16 | "url": "http://github.com/bojand/mailgun-js/issues"
17 | },
18 | "engines": {
19 | "node": ">=6.0.0"
20 | },
21 | "main": "./lib/mailgun.js",
22 | "dependencies": {
23 | "async": "^2.6.1",
24 | "debug": "^4.1.0",
25 | "form-data": "^2.3.3",
26 | "inflection": "~1.12.0",
27 | "is-stream": "^1.1.0",
28 | "path-proxy": "~1.0.0",
29 | "promisify-call": "^2.0.2",
30 | "proxy-agent": "^3.1.1",
31 | "tsscmp": "^1.0.6"
32 | },
33 | "author": {
34 | "name": "Bojan Djurkovic "
35 | },
36 | "devDependencies": {
37 | "clone": "^2.1.2",
38 | "mocha": "~5.2.0",
39 | "nodemailer": "^4.6.8",
40 | "request": "^2.88.0",
41 | "sinon": "^7.1.0",
42 | "standard": "^12.0.0"
43 | },
44 | "scripts": {
45 | "test": "npm run lint && npm run mocha",
46 | "mocha": "mocha --ui exports --slow 1500ms --timeout 10000ms --reporter spec",
47 | "lint": "standard lib/**/*.js && standard --env mocha test/**/*.js",
48 | "lint:fix": "standard --fix lib/**/*.js && standard --env mocha --fix test/**/*.js",
49 | "docs": "./bin/docs"
50 | },
51 | "directories": {
52 | "test": "test"
53 | },
54 | "standard": {
55 | "env": {
56 | "node": true,
57 | "mocha": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/api/members.md:
--------------------------------------------------------------------------------
1 | # members
2 |
3 | Programatically work with mailing lists members.
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | Fetches the list of mailing list members.
10 |
11 | `mailgun.lists({address}).members().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /lists/{address}/members
16 |
17 | ### `page`
18 |
19 | Paginate over list members in the given mailing list
20 |
21 | `mailgun.lists({address}).members().pages().page({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /lists/{address}/members/pages
26 |
27 | ### `info`
28 |
29 | Retrieves a mailing list member.
30 |
31 | `mailgun.lists({address}).members({member_address}).info({callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | GET | /lists/{address}/members/{member_address}
36 |
37 | ### `create`
38 |
39 | Adds a member to the mailing list.
40 |
41 | `mailgun.lists({address}).members().create({attributes}, {callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | POST | /lists/{address}/members
46 |
47 | ### `add`
48 |
49 | Adds multiple members, up to 1,000 per call, to a Mailing List.
50 |
51 | `mailgun.lists({address}).members().add({attributes}, {callback});`
52 |
53 | Method | Path
54 | --- | ---
55 | POST | /lists/{address}/members.json
56 |
57 | ### `update`
58 |
59 | Updates a mailing list member with given properties.
60 |
61 | `mailgun.lists({address}).members({member_address}).update({attributes}, {callback});`
62 |
63 | Method | Path
64 | --- | ---
65 | PUT | /lists/{address}/members/{member_address}
66 |
67 | ### `delete`
68 |
69 | Delete a mailing list member.
70 |
71 | `mailgun.lists({address}).members({member_address}).delete({callback});`
72 |
73 | Method | Path
74 | --- | ---
75 | DELETE | /lists/{address}/members/{member_address}
76 |
77 |
--------------------------------------------------------------------------------
/docs/api/tags.md:
--------------------------------------------------------------------------------
1 | # tags
2 |
3 | Deletes all counters for particular tag and the tag itself. See http://documentation.mailgun.com/api-stats.html
4 |
5 | ## Actions
6 |
7 | ### `list`
8 |
9 | List all tags.
10 |
11 | `mailgun.tags().list({callback});`
12 |
13 | Method | Path
14 | --- | ---
15 | GET | /{domain}/tags
16 |
17 | ### `info`
18 |
19 | Gets a specific tag.
20 |
21 | `mailgun.tags({tag}).info({callback});`
22 |
23 | Method | Path
24 | --- | ---
25 | GET | /{domain}/tags/{tag}
26 |
27 | ### `info`
28 |
29 | Returns statistics for a given tag.
30 |
31 | `mailgun.tags({tag}).stats().info({callback});`
32 |
33 | Method | Path
34 | --- | ---
35 | GET | /{domain}/tags/{tag}/stats
36 |
37 | ### `list`
38 |
39 | Returns a list of countries of origin for a given domain for different event types.
40 |
41 | `mailgun.tags({tag}).stats().aggregates().countries().list({callback});`
42 |
43 | Method | Path
44 | --- | ---
45 | GET | /{domain}/tags/{tag}/stats/aggregates/countries
46 |
47 | ### `list`
48 |
49 | Returns a list of email providers for a given domain for different event types.
50 |
51 | `mailgun.tags({tag}).stats().aggregates().providers().list({callback});`
52 |
53 | Method | Path
54 | --- | ---
55 | GET | /{domain}/tags/{tag}/stats/aggregates/providers
56 |
57 | ### `list`
58 |
59 | Returns a list of devices for a given domain that have triggered event types.
60 |
61 | `mailgun.tags({tag}).stats().aggregates().devices().list({callback});`
62 |
63 | Method | Path
64 | --- | ---
65 | GET | /{domain}/tags/{tag}/stats/aggregates/devices
66 |
67 | ### `delete`
68 |
69 | Deletes all counters for particular tag and the tag itself.
70 |
71 | `mailgun.tags({tag}).delete({callback});`
72 |
73 | Method | Path
74 | --- | ---
75 | DELETE | /{domain}/tags/{tag}
76 |
77 |
--------------------------------------------------------------------------------
/test/bounces.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | describe('Bounces', () => {
11 | beforeEach((done) => {
12 | setTimeout(done, 500)
13 | })
14 |
15 | it('test bounces().create() missing address', (done) => {
16 | mailgun.bounces().create({}, (err) => {
17 | assert.ok(err)
18 | assert(/Missing parameter 'address'/.test(err.message))
19 | done()
20 | })
21 | })
22 |
23 | it('test bounces().create() invalid address type', (done) => {
24 | mailgun.bounces().create({
25 | 'address': 123
26 | }, (err) => {
27 | assert.ok(err)
28 | assert(/Invalid parameter type./.test(err.message))
29 | done()
30 | })
31 | })
32 |
33 | it('test bounces().create()', (done) => {
34 | mailgun.bounces().create(fixture.bounce, (err, body) => {
35 | assert.ifError(err)
36 | assert.ok(body)
37 | done()
38 | })
39 | })
40 |
41 | it('test bounces().list()', (done) => {
42 | mailgun.bounces().list((err, body) => {
43 | assert.ifError(err)
44 | assert.ok(body.items)
45 | done()
46 | })
47 | })
48 |
49 | it('test bounces().info()', (done) => {
50 | mailgun.bounces(fixture.bounce.address).info((err, body) => {
51 | assert.ifError(err)
52 | assert.ok(body)
53 | assert.ok(body.address)
54 | done()
55 | })
56 | })
57 |
58 | it('test bounces().delete()', (done) => {
59 | mailgun.bounces(fixture.bounce.address).delete((err, body) => {
60 | assert.ifError(err)
61 | assert.ok(body)
62 | done()
63 | })
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/test/data/fixture.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": {
3 | "to": "mailgunjs+test1@gmail.com",
4 | "from": "mailgunjstest+recv1@gmail.com",
5 | "subject": "Test email subject",
6 | "text": "Test email text"
7 | },
8 | "message_recipient_vars": {
9 | "to": [
10 | "mailgunjs+test1@gmail.com",
11 | "mailgunjs+test2@gmail.com"
12 | ],
13 | "from": "mailgunjstest+send@gmail.com",
14 | "subject": "Hey, %recipient.first%",
15 | "text": "Test email text. Your ID: %recipient.id%",
16 | "recipient-variables": {
17 | "mailgunjs+test1@gmail.com": {
18 | "first": "Bob",
19 | "id": 1
20 | },
21 | "mailgunjs+test2@gmail.com": {
22 | "first": "Alice",
23 | "id": 2
24 | }
25 | }
26 | },
27 | "route": {
28 | "description": "Sample test route",
29 | "expression": "match_recipient('.*@sandbox77047.mailgun.org')",
30 | "action": "forward('http://myhost.com/messages/')"
31 | },
32 | "mailingList": {
33 | "address": "devtest@sandbox77047.mailgun.org",
34 | "name": "MailgunDev",
35 | "description": "Mailgun developers list"
36 | },
37 | "new_domain": {
38 | "name": "mgjsunits1234.mailgun.org"
39 | },
40 | "existing_domain": {
41 | "name": "sandbox77047.mailgun.org"
42 | },
43 | "credentials": {
44 | "login": "user01@sandbox77047.mailgun.org",
45 | "password": "12345"
46 | },
47 | "unsubscribe": {
48 | "address": "me@test.mailgun.org",
49 | "tag": "*"
50 | },
51 | "unsubscribe2": {
52 | "address": "me2@test.mailgun.org",
53 | "tag": ["some_tag"]
54 | },
55 | "bounce": {
56 | "address": "me@test.mailgun.org"
57 | },
58 | "campaign": {
59 | "name": "My test campaign",
60 | "id": "my_unique_campaign_id",
61 | "newName": "my_new_campaign_name"
62 | },
63 | "complaints": {
64 | "address": "testcomplaint@faketest.com"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/test/tags.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Tags', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('test mailgun.tags().list()', (done) => {
15 | mailgun.tags().list((err, body) => {
16 | assert.ifError(err)
17 | assert.ok(body)
18 | done()
19 | })
20 | })
21 |
22 | it('test mailgun.tags().info()', (done) => {
23 | mailgun.tags('tag1').info((err, body) => {
24 | assert.ifError(err)
25 | assert.ok(body)
26 | done()
27 | })
28 | })
29 |
30 | it('test mailgun.tags().stats().info()', (done) => {
31 | mailgun.tags('tag1').stats().info({
32 | event: ['accepted', 'delivered', 'opened', 'clicked']
33 | }, (err, body) => {
34 | assert.ifError(err)
35 | assert.ok(body)
36 | done()
37 | })
38 | })
39 |
40 | it('test mailgun.tags().aggregates().countries().list()', (done) => {
41 | mailgun.tags('tag1').stats().aggregates().countries().list((err, body) => {
42 | assert.ifError(err)
43 | assert.ok(body)
44 | assert.ok(body.countries)
45 | done()
46 | })
47 | })
48 |
49 | it('test mailgun.tags().aggregates().providers().list()', (done) => {
50 | mailgun.tags('tag1').stats().aggregates().providers().list((err, body) => {
51 | assert.ifError(err)
52 | assert.ok(body)
53 | assert.ok(body.providers)
54 | done()
55 | })
56 | })
57 |
58 | it('test mailgun.tags().aggregates().devices().list()', (done) => {
59 | mailgun.tags('tag1').stats().aggregates().devices().list((err, body) => {
60 | assert.ifError(err)
61 | assert.ok(body)
62 | assert.ok(body.devices)
63 | done()
64 | })
65 | })
66 |
67 | it('test mailgun.tags().delete()', (done) => {
68 | mailgun.tags('tag1').delete((err, body) => {
69 | assert.ifError(err)
70 | assert.ok(body.message)
71 | done()
72 | })
73 | })
74 | })
75 |
--------------------------------------------------------------------------------
/docs/batch.md:
--------------------------------------------------------------------------------
1 | ## Batch Sending
2 |
3 | Mailgun-js can be used for batch sending. Please see Mailgun [documentation](https://documentation.mailgun.com/user_manual.html#batch-sending) for more details.
4 | Example source code:
5 |
6 | ```js
7 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
8 |
9 | var recipientVars = {
10 | 'bob@companyx.com': {
11 | first: 'Bob',
12 | id: 1
13 | },
14 | 'alice@companyy.com': {
15 | first: 'Alice',
16 | id: 2
17 | }
18 | };
19 |
20 | var data = {
21 | from: 'Excited User ',
22 | to: ['bob@companyx.com', 'alice@companyy.com'],
23 | subject: 'Hey, %recipient.first%',
24 | 'recipient-variables': recipientVars,
25 | text: 'If you wish to unsubscribe, click http://mailgun/unsubscribe/%recipient.id%',
26 | };
27 |
28 | mailgun.messages().send(data, function (error, body) {
29 | console.log(body);
30 | });
31 | ```
32 |
33 | When sending using `sendMime()` we have to set the MIME header `X-Mailgun-Recipient-Variables` appropriately to the recipient variables data. For example using [mailcomposer](https://nodemailer.com/extras/mailcomposer/):
34 |
35 | ```js
36 | const MailComposer = require('nodemailer/lib/mail-composer')
37 | const mailgun = require('mailgun-js')({ apiKey: api_key, domain: domain })
38 |
39 | const toArray = ['recipient+test1@gmail.com', 'recipient+test2@gmail.com']
40 |
41 | const recipientVars = {
42 | 'recipient+test1@gmail.com': {
43 | first: 'Recv1',
44 | id: 1
45 | },
46 | 'recipient+test2@gmail.com': {
47 | first: 'Recv2',
48 | id: 2
49 | }
50 | }
51 |
52 | const mailOptions = {
53 | from: 'test+sender@gmail.com',
54 | to: toArray,
55 | subject: 'Hey, %recipient.first%',
56 | text: 'Hello %recipient.id%',
57 | html: 'Hello %recipient.id%',
58 | headers: {
59 | 'X-Mailgun-Recipient-Variables': JSON.stringify(recipientVars)
60 | }
61 | }
62 |
63 | const mail = new MailComposer(mailOptions)
64 | mail.compile().build(function (err, message) {
65 | const data = {
66 | to: toArray,
67 | message: message.toString('ascii'),
68 | 'recipient-variables': recipientVars
69 | }
70 |
71 | mailgun.messages().sendMime(data, function (err, body) {
72 | console.log(body)
73 | })
74 | })
75 | ```
76 |
--------------------------------------------------------------------------------
/docs/testmode.md:
--------------------------------------------------------------------------------
1 | ## Test mode
2 |
3 | Test mode can be turned on using `testMode` option. When on, no requests are actually sent to Mailgun, rather we log the request options and applicable payload and form data. By default we log to `console.log`, unless `DEBUG` is turned on, in which case we use debug logging.
4 |
5 | ```js
6 | mailgun = require('mailgun-js')({ apiKey: api_key, domain: domain, testMode: true })
7 |
8 | const data = {
9 | from: 'mailgunjs+test1@gmail.com',
10 | to: 'mailgunjstest+recv1@gmail.com',
11 | subject: 'Test email subject',
12 | text: 'Test email text'
13 | };
14 |
15 | mailgun.messages().send(data, function (error, body) {
16 | console.log(body);
17 | });
18 | ```
19 |
20 | ```
21 | options: { hostname: 'api.mailgun.net',
22 | port: 443,
23 | protocol: 'https:',
24 | path: '/v3/sandbox12345.mailgun.org/messages',
25 | method: 'POST',
26 | headers:
27 | { 'Content-Type': 'application/x-www-form-urlencoded',
28 | 'Content-Length': 127 },
29 | auth: 'api:key-0e8pwgtt5ylx0m94xwuzqys2-o0x4-77',
30 | agent: false,
31 | timeout: undefined }
32 | payload: 'to=mailgunjs%2Btest1%40gmail.com&from=mailgunjstest%2Brecv1%40gmail.com&subject=Test%20email%20subject&text=Test%20email%20text'
33 | form: undefined
34 | undefined
35 | ```
36 |
37 | Note that in test mode no error or body are returned as a result.
38 |
39 | The logging can be customized using `testModeLogger` option which is a function to perform custom logging.
40 |
41 | ```js
42 | const logger = (httpOptions, payload, form) => {
43 | const { method, path } = httpOptions
44 | const hasPayload = !!payload
45 | const hasForm = !!form
46 |
47 | console.log(`%s %s payload: %s form: %s`, method, path, hasPayload, hasForm)
48 | }
49 |
50 | mailgun = require('mailgun-js')({ apiKey: api_key, domain: domain, testMode: true, testModeLogger: logger })
51 |
52 | const data = {
53 | from: 'mailgunjs+test1@gmail.com',
54 | to: 'mailgunjstest+recv1@gmail.com',
55 | subject: 'Test email subject',
56 | text: 'Test email text'
57 | };
58 |
59 | mailgun.messages().send(data, function (error, body) {
60 | console.log(body);
61 | });
62 | ```
63 |
64 | Sample output:
65 |
66 | ```
67 | POST /v3/sandbox12345.mailgun.org/messages payload: true form: false
68 | undefined
69 | ```
70 |
--------------------------------------------------------------------------------
/test/routes.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | let routeId = -1
11 |
12 | describe('Routes', () => {
13 | beforeEach((done) => {
14 | setTimeout(done, 500)
15 | })
16 |
17 | it('test routes().create() invalid missing expression', (done) => {
18 | mailgun.routes().create({}, (err) => {
19 | assert.ok(err)
20 | assert(/Missing parameter 'expression'/.test(err.message))
21 | done()
22 | })
23 | })
24 |
25 | it('test routes().create()', (done) => {
26 | mailgun.routes().create(fixture.route, (err, body) => {
27 | assert.ifError(err)
28 | assert.ok(body)
29 | assert.ok(body.route)
30 | routeId = body.route.id
31 | done()
32 | })
33 | })
34 |
35 | it('test routes().list()', (done) => {
36 | mailgun.routes().list((err, body) => {
37 | assert.ifError(err)
38 | assert.ok(body.total_count >= 0)
39 | assert.ok(body.items)
40 | done()
41 | })
42 | })
43 |
44 | it('test routes().info()', (done) => {
45 | mailgun.routes(routeId).info((err, body) => {
46 | assert.ifError(err)
47 | assert.ok(body.route)
48 | assert.strictEqual(routeId, body.route.id)
49 | done()
50 | })
51 | })
52 |
53 | it('test routes().update() with one action argument', (done) => {
54 | mailgun.routes(routeId).update({
55 | 'description': 'new route update',
56 | 'expression': 'match_recipient(".*@samples.mailgun.org")',
57 | 'action': 'forward("http://myhost.com/messages/")'
58 | }, (err, body) => {
59 | assert.ifError(err)
60 | assert.ok(body.message)
61 | assert.strictEqual(routeId, body.id)
62 | done()
63 | })
64 | })
65 |
66 | it('test routes().update() with two action arguments', (done) => {
67 | mailgun.routes(routeId).update({
68 | 'description': 'test new route update',
69 | 'expression': 'match_recipient(".*@samples.mailgun.org")',
70 | 'action': ['forward("http://myhost.com/messages/")', 'stop()']
71 | }, (err, body) => {
72 | assert.ifError(err)
73 | assert.ok(body.message)
74 | assert.strictEqual(routeId, body.id)
75 | done()
76 | })
77 | })
78 |
79 | it('test routes().delete()', (done) => {
80 | mailgun.routes(routeId).delete((err, body) => {
81 | assert.ifError(err)
82 | assert.ok(body.message)
83 | done()
84 | })
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/test/validation.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const assert = require('assert')
3 |
4 | const mailgun = require('../')({
5 | 'apiKey': auth.api_key,
6 | 'domain': auth.domain
7 | })
8 |
9 | describe('Email Validation', () => {
10 | beforeEach((done) => {
11 | setTimeout(done, 500)
12 | })
13 |
14 | it('test mailgun.validate() should validate one email address', (done) => {
15 | const mg = new mailgun.Mailgun({
16 | 'apiKey': auth.api_key,
17 | 'publicApiKey': auth.public_api_key,
18 | 'domain': auth.domain
19 | })
20 |
21 | mg.validate('raboof@gmail.com', (err, body) => {
22 | assert.ifError(err)
23 | assert.ok(body)
24 | done()
25 | })
26 | })
27 |
28 | it('test mailgun.validate() should validate one email address using private option', (done) => {
29 | const mg = new mailgun.Mailgun({
30 | 'apiKey': auth.api_key,
31 | 'publicApiKey': auth.public_api_key,
32 | 'domain': auth.domain
33 | })
34 |
35 | mg.validate('raboof@gmail.com', true, (err, body) => {
36 | assert.ifError(err)
37 | assert.ok(body)
38 | done()
39 | })
40 | })
41 |
42 | it('test mailgun.parse() should parse list of email addresses', (done) => {
43 | const mg = new mailgun.Mailgun({
44 | 'apiKey': auth.api_key,
45 | 'publicApiKey': auth.public_api_key,
46 | 'domain': auth.domain
47 | })
48 |
49 | mg.parse(['raboof@gmail.com'], (err, body) => {
50 | assert.ifError(err)
51 | assert.ok(body)
52 | done()
53 | })
54 | })
55 |
56 | it('test mailgun.parse() should parse list of email addresses with an option', (done) => {
57 | const mg = new mailgun.Mailgun({
58 | 'apiKey': auth.api_key,
59 | 'publicApiKey': auth.public_api_key,
60 | 'domain': auth.domain
61 | })
62 |
63 | mg.parse(['raboof@gmail.com'], {
64 | 'syntax_only': false
65 | }, (err, body) => {
66 | assert.ifError(err)
67 | assert.ok(body)
68 | done()
69 | })
70 | })
71 |
72 | it('test mailgun.parse() should private parse list of email addresses with an option', (done) => {
73 | const mg = new mailgun.Mailgun({
74 | 'apiKey': auth.api_key,
75 | 'publicApiKey': auth.public_api_key,
76 | 'domain': auth.domain
77 | })
78 |
79 | mg.parse(['raboof@gmail.com'], true, {
80 | 'syntax_only': false
81 | }, (err, body) => {
82 | assert.ifError(err)
83 | assert.ok(body)
84 | done()
85 | })
86 | })
87 |
88 | it('test mailgun.parse() should parse list of email addresses using private option', (done) => {
89 | const mg = new mailgun.Mailgun({
90 | 'apiKey': auth.api_key,
91 | 'publicApiKey': auth.public_api_key,
92 | 'domain': auth.domain
93 | })
94 |
95 | mg.parse(['raboof@gmail.com'], true, (err, body) => {
96 | assert.ifError(err)
97 | assert.ok(body)
98 | done()
99 | })
100 | })
101 | })
102 |
--------------------------------------------------------------------------------
/test/sendmime.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 | const fs = require('fs')
5 | const path = require('path')
6 | const MailComposer = require('nodemailer/lib/mail-composer')
7 |
8 | const mailgun = require('../')({
9 | 'apiKey': auth.api_key,
10 | 'domain': auth.domain
11 | })
12 |
13 | describe('Send MIME', () => {
14 | beforeEach((done) => {
15 | setTimeout(done, 500)
16 | })
17 |
18 | it('test sendMime()', (done) => {
19 | const mailOptions = {
20 | 'from': fixture.message.from,
21 | 'to': fixture.message.to,
22 | 'subject': fixture.message.subject,
23 | 'body': fixture.message.text,
24 | 'html': `${fixture.message.text}`
25 | }
26 |
27 | const mail = new MailComposer(mailOptions)
28 |
29 | mail.compile().build((err, message) => {
30 | assert.ifError(err)
31 |
32 | const data = {
33 | 'to': fixture.message.to,
34 | 'message': message.toString('ascii')
35 | }
36 |
37 | mailgun.messages().sendMime(data, (err, body) => {
38 | assert.ifError(err)
39 | assert.ok(body.id)
40 | assert.ok(body.message)
41 | assert(/Queued. Thank you./.test(body.message))
42 | done()
43 | })
44 | })
45 | })
46 |
47 | it('test sendMime() with to field as array', (done) => {
48 | const mailOptions = {
49 | 'from': fixture.message.from,
50 | 'to': fixture.message.to,
51 | 'subject': fixture.message.subject,
52 | 'body': fixture.message.text,
53 | 'html': `${fixture.message.text}`
54 | }
55 |
56 | const mail = new MailComposer(mailOptions)
57 |
58 | mail.compile().build((err, message) => {
59 | assert.ifError(err)
60 |
61 | const data = {
62 | 'to': fixture.message_recipient_vars.to,
63 | 'message': message.toString('ascii')
64 | }
65 |
66 | mailgun.messages().sendMime(data, (err, body) => {
67 | assert.ifError(err)
68 | assert.ok(body.id)
69 | assert.ok(body.message)
70 | assert(/Queued. Thank you./.test(body.message))
71 | done()
72 | })
73 | })
74 | })
75 |
76 | it('test sendMime() with file path', (done) => {
77 | const filePath = path.join(__dirname, '/data/message.eml')
78 |
79 | const data = {
80 | 'to': fixture.message.to,
81 | 'message': filePath
82 | }
83 |
84 | mailgun.messages().sendMime(data, (err, body) => {
85 | assert.ifError(err)
86 | assert.ok(body.id)
87 | assert.ok(body.message)
88 | assert(/Queued. Thank you./.test(body.message))
89 | done()
90 | })
91 | })
92 |
93 | it('test sendMime() with file stream', (done) => {
94 | const filePath = path.join(__dirname, '/data/message.eml')
95 |
96 | const data = {
97 | 'to': fixture.message.to,
98 | 'message': fs.createReadStream(filePath)
99 | }
100 |
101 | mailgun.messages().sendMime(data, (err, body) => {
102 | assert.ifError(err)
103 | assert.ok(body.id)
104 | assert.ok(body.message)
105 | assert(/Queued. Thank you./.test(body.message))
106 | done()
107 | })
108 | })
109 | })
110 |
--------------------------------------------------------------------------------
/lib/build.js:
--------------------------------------------------------------------------------
1 | const inflection = require('inflection')
2 | const pathProxy = require('path-proxy')
3 | const promisifyCall = require('promisify-call')
4 |
5 | class Builder {
6 | constructor (baseObj, resources) {
7 | this.baseObj = baseObj
8 | this.resources = resources
9 | }
10 |
11 | build () {
12 | Object.keys(this.resources).forEach((key) => {
13 | // console.log('building ' + key);
14 | this.buildResource(this.resources[key])
15 | })
16 | }
17 |
18 | buildResource (resource) {
19 | resource.links.forEach(this.buildAction, this)
20 | }
21 |
22 | buildAction (action) {
23 | const actionName = action.title
24 | const properties = action.properties
25 | const requiredProps = action.required
26 |
27 | // HACKY special case for members bulk add and send MIME endpoints
28 | const path = action.href.replace(/\.json/gi, '').replace(/\.mime/gi, '')
29 | const constructor = pathProxy.pathProxy(this.baseObj, path)
30 |
31 | function impl (data, fn) {
32 | let requestPath = action.href
33 | const pathParams = action.href.match(/{[^}]+}/g) || []
34 |
35 | if (typeof data === 'function') {
36 | fn = data
37 | data = undefined
38 | }
39 |
40 | let err
41 |
42 | if (this.params.length !== pathParams.length) {
43 | err = new Error(`Invalid number of params in path (expected ${pathParams.length}, got ${this.params.length}).`)
44 |
45 | return fn(err)
46 | }
47 |
48 | this.params.forEach((param) => {
49 | requestPath = requestPath.replace(/{[^}]+}/, param)
50 | })
51 |
52 | // check required payload properties
53 | if (requiredProps && requiredProps.length > 0) {
54 | if (!data) {
55 | err = new Error('Missing parameters.')
56 | } else {
57 | for (let i = 0; i < requiredProps.length; i++) {
58 | const prop = requiredProps[i]
59 |
60 | if (typeof data[prop] === 'undefined') {
61 | err = new Error(`Missing parameter '${prop}'`)
62 | break
63 | }
64 | }
65 | }
66 | }
67 |
68 | if (err) {
69 | return fn(err)
70 | }
71 |
72 | // check payload property types
73 | for (const key in properties) {
74 | if (data && data[key]) {
75 | const type = properties[key].type
76 |
77 | let dataType = typeof data[key]
78 |
79 | if (Array.isArray(data[key])) {
80 | dataType = 'array'
81 | }
82 |
83 | if (Array.isArray(type)) {
84 | if (type.indexOf(dataType) === -1) {
85 | err = new Error(`Invalid parameter type. ${key} must be of type: ${type}.`)
86 | break
87 | }
88 | } else if (dataType !== type) {
89 | err = new Error(`Invalid parameter type. ${key} must be of type: ${type}.`)
90 | break
91 | }
92 | }
93 | }
94 |
95 | if (err) {
96 | return fn(err)
97 | }
98 |
99 | this.client = this.base
100 |
101 | return this.client.request(action.method, requestPath, data, fn)
102 | }
103 |
104 | function promisifed (data, fn) {
105 | return promisifyCall(this, impl, data, fn)
106 | }
107 |
108 | constructor.prototype[getName(actionName)] = promisifed
109 | }
110 | }
111 |
112 | function getName (name) {
113 | name = name.toLowerCase()
114 | name = inflection.dasherize(name).replace(/-/g, '_')
115 | name = inflection.camelize(name, true)
116 |
117 | return name
118 | }
119 |
120 | function build (baseObj, resources) {
121 | const b = new Builder(baseObj, resources)
122 |
123 | b.build()
124 | }
125 |
126 | exports.build = build
127 |
--------------------------------------------------------------------------------
/test/testmode.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const clone = require('clone')
4 | const assert = require('assert')
5 | const path = require('path')
6 | const debug = require('debug')
7 | const sinon = require('sinon')
8 |
9 | let mailgun
10 | let log, sandbox
11 |
12 | describe('test mode', () => {
13 | describe('default operation', () => {
14 | before(() => {
15 | mailgun = require('../')({
16 | apiKey: auth.api_key,
17 | domain: auth.domain,
18 | testMode: true
19 | })
20 | })
21 |
22 | beforeEach(done => {
23 | setTimeout(done, 100)
24 | })
25 |
26 | it('test messages().send()', done => {
27 | const msg = clone(fixture.message)
28 |
29 | mailgun.messages().send(msg, (err, body) => {
30 | assert.strictEqual(err, undefined)
31 | assert.strictEqual(body, undefined)
32 |
33 | done()
34 | })
35 | })
36 |
37 | it('test messages().send() with attachment using filename', done => {
38 | const msg = clone(fixture.message)
39 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
40 |
41 | msg.attachment = filename
42 |
43 | mailgun.messages().send(msg, (err, body) => {
44 | assert.strictEqual(err, undefined)
45 | assert.strictEqual(body, undefined)
46 |
47 | done()
48 | })
49 | })
50 | })
51 |
52 | describe('default operation with DEBUG', () => {
53 | before(() => {
54 | mailgun = require('../')({
55 | apiKey: auth.api_key,
56 | domain: auth.domain,
57 | testMode: true
58 | })
59 |
60 | debug.enable('mailgun-js')
61 | })
62 |
63 | after(() => {
64 | debug.disable()
65 | })
66 |
67 | beforeEach(done => {
68 | setTimeout(done, 100)
69 | })
70 |
71 | it('test messages().send()', done => {
72 | const msg = clone(fixture.message)
73 |
74 | mailgun.messages().send(msg, (err, body) => {
75 | assert.strictEqual(err, undefined)
76 | assert.strictEqual(body, undefined)
77 |
78 | done()
79 | })
80 | })
81 | })
82 |
83 | describe('custom logger', () => {
84 | before(() => {
85 | mailgun = require('../')({
86 | apiKey: auth.api_key,
87 | domain: auth.domain,
88 | testMode: true,
89 | testModeLogger: (httpOptions, payload, form) => {
90 | const { method, path } = httpOptions
91 | const hasPayload = !!payload
92 | const hasForm = !!form
93 |
94 | console.log(`%s %s payload: %s form: %s`, method, path, hasPayload, hasForm)
95 | }
96 | })
97 | })
98 |
99 | beforeEach(done => {
100 | sandbox = sinon.createSandbox()
101 | log = sandbox.spy(console, 'log')
102 |
103 | setTimeout(done, 100)
104 | })
105 |
106 | afterEach(() => {
107 | sandbox.restore()
108 | })
109 |
110 | it('test messages().send()', done => {
111 | const msg = clone(fixture.message)
112 |
113 | mailgun.messages().send(msg, (err, body) => {
114 | assert.strictEqual(err, undefined)
115 | assert.strictEqual(body, undefined)
116 |
117 | assert.ok(log.calledOnce)
118 | assert.ok(
119 | log.calledWith(
120 | `%s %s payload: %s form: %s`,
121 | 'POST',
122 | '/v3/sandbox77047.mailgun.org/messages',
123 | true,
124 | false
125 | )
126 | )
127 |
128 | done()
129 | })
130 | })
131 |
132 | it('test messages().send() with attachment using filename', done => {
133 | const msg = clone(fixture.message)
134 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
135 |
136 | msg.attachment = filename
137 |
138 | mailgun.messages().send(msg, (err, body) => {
139 | assert.strictEqual(err, undefined)
140 | assert.strictEqual(body, undefined)
141 |
142 | assert.ok(log.calledOnce)
143 | assert.ok(
144 | log.calledWith(
145 | `%s %s payload: %s form: %s`,
146 | 'POST',
147 | '/v3/sandbox77047.mailgun.org/messages',
148 | false,
149 | true
150 | )
151 | )
152 |
153 | done()
154 | })
155 | })
156 | })
157 | })
158 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # mailgun.js
2 |
3 | Simple Node.js module for [Mailgun](http://www.mailgun.com).
4 |
5 | [](https://www.npmjs.com/package/mailgun-js)
6 | [](https://standardjs.com)
7 | [](https://raw.githubusercontent.com/bojand/mailgun-js/master/LICENSE.txt)
8 | [](https://www.paypal.me/bojandj)
9 | [](https://www.buymeacoffee.com/bojand)
10 |
11 | ## Installation
12 |
13 | `npm install mailgun-js`
14 |
15 | ## Usage overview
16 |
17 | This is a simple Node.js module for interacting with the [Mailgun](http://www.mailgun.com) API. This module is intended to be used within Node.js environment and **not** from the browser. For browser use the [mailgun.js](https://github.com/mailgun/mailgun-js) module.
18 |
19 | Please see [Mailgun Documentation](https://documentation.mailgun.com) for full Mailgun API reference.
20 |
21 | This module works by providing proxy objects for interacting with different resources through the Mailgun API.
22 | Most methods take a `data` parameter, which is a Javascript object that would contain the arguments for the Mailgun API.
23 | All methods take a final parameter callback with two parameters: `error`, and `body`.
24 | We try to parse the `body` into a javascript object, and return it to the callback as such for easier use and inspection by the client.
25 | If there was an error a new `Error` object will be passed to the callback in the `error` parameter.
26 | If the error originated from the (Mailgun) server, the response code will be available in the `statusCode` property
27 | of the `error` object passed in the callback.
28 | See the `/docs` folder for detailed documentation. For full usage examples see the `/test` folder.
29 |
30 | ```js
31 | var api_key = 'XXXXXXXXXXXXXXXXXXXXXXX';
32 | var domain = 'www.mydomain.com';
33 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
34 |
35 | var data = {
36 | from: 'Excited User ',
37 | to: 'serobnic@mail.ru',
38 | subject: 'Hello',
39 | text: 'Testing some Mailgun awesomeness!'
40 | };
41 |
42 | mailgun.messages().send(data, function (error, body) {
43 | console.log(body);
44 | });
45 | ```
46 |
47 | Note that the `to` field is required and should contain all recipients ("TO", "CC" and "BCC") of the message (see https://documentation.mailgun.com/api-sending.html#sending). Additionally `cc` and `bcc` fields can be specified. Recipients in those fields will be addressed as such.
48 |
49 | Messages stored using the Mailgun `store()` action can be retrieved using `messages().info()` function.
50 | Optionally the MIME representation of the message can be retrieved if `MIME` argument is passed in and set to `true`.
51 |
52 | Something more elaborate. Get mailing list info, create a member and get mailing list members and update member.
53 | Notice that the proxy objects can be reused.
54 |
55 | ```js
56 | var list = mailgun.lists('mylist@mycompany.com');
57 |
58 | list.info(function (err, data) {
59 | // `data` is mailing list info
60 | console.log(data);
61 | });
62 |
63 | var bob = {
64 | subscribed: true,
65 | address: 'bob@gmail.com',
66 | name: 'Bob Bar',
67 | vars: {age: 26}
68 | };
69 |
70 | list.members().create(bob, function (err, data) {
71 | // `data` is the member details
72 | console.log(data);
73 | });
74 |
75 | list.members().list(function (err, members) {
76 | // `members` is the list of members
77 | console.log(members);
78 | });
79 |
80 | list.members('bob@gmail.com').update({ name: 'Foo Bar' }, function (err, body) {
81 | console.log(body);
82 | });
83 | ```
84 |
85 | #### Options
86 |
87 | `Mailgun` object constructor options:
88 |
89 | * `apiKey` - Your Mailgun API KEY
90 | * `publicApiKey` - Your public Mailgun API KEY
91 | * `domain` - Your Mailgun Domain (Please note: domain field is `MY-DOMAIN-NAME.com`, not https://api.mailgun.net/v3/MY-DOMAIN-NAME.com)
92 | * `mute` - Set to `true` if you wish to mute the console error logs in `validateWebhook()` function
93 | * `proxy` - The proxy URI in format `http[s]://[auth@]host:port`. ex: `'http://proxy.example.com:8080'`
94 | * `timeout` - Request timeout in milliseconds
95 | * `host` - the mailgun host (default: 'api.mailgun.net')
96 | * `protocol` - the mailgun protocol (default: 'https:', possible values: 'http:' or 'https:')
97 | * `port` - the mailgun port (default: '443')
98 | * `endpoint` - the mailgun host (default: '/v3')
99 | * `retry` - the number of **total attempts** to do when performing requests. Default is `1`.
100 | That is, we will try an operation only once with no retries on error.
101 |
102 | ## Notes
103 |
104 | This project is not endorsed by or affiliated with [Mailgun](http://www.mailgun.com).
105 | The general design and some code was heavily inspired by [node-heroku-client](https://github.com/jclem/node-heroku-client).
106 |
107 | ## License
108 |
109 | Copyright (c) 2012 - 2017 OneLobby and Bojan D.
110 |
111 | Licensed under the MIT License.
112 |
--------------------------------------------------------------------------------
/docs/attach.md:
--------------------------------------------------------------------------------
1 | ### Attachments
2 |
3 | Attachments can be sent using either the `attachment` or `inline` parameters. `inline` parameter can be use to send an
4 | attachment with `inline` disposition. It can be used to send inline images. Both types are supported with same mechanisms
5 | as described, we will just use `attachment` parameter in the documentation below but same stands for `inline`.
6 |
7 | Sending attachments can be done in a few ways. We can use the path to a file in the `attachment` parameter.
8 | If the `attachment` parameter is of type `string` it is assumed to be the path to a file.
9 |
10 | ```js
11 | var filepath = path.join(__dirname, 'mailgun_logo.png');
12 |
13 | var data = {
14 | from: 'Excited User ',
15 | to: 'serobnic@mail.ru',
16 | subject: 'Hello',
17 | text: 'Testing some Mailgun awesomeness!',
18 | attachment: filepath
19 | };
20 |
21 | mailgun.messages().send(data, function (error, body) {
22 | console.log(body);
23 | });
24 | ```
25 |
26 | We can pass a buffer (has to be a `Buffer` object) of the data. If a buffer is used the data will be attached using a
27 | generic filename "file".
28 |
29 | ```js
30 | var filepath = path.join(__dirname, 'mailgun_logo.png');
31 | var file = fs.readFileSync(filepath);
32 |
33 | var data = {
34 | from: 'Excited User ',
35 | to: 'serobnic@mail.ru',
36 | subject: 'Hello',
37 | text: 'Testing some Mailgun awesomeness!',
38 | attachment: file
39 | };
40 |
41 | mailgun.messages().send(data, function (error, body) {
42 | console.log(body);
43 | });
44 | ```
45 |
46 | We can also pass in a stream of the data. This is useful if you're attaching a file from the internet.
47 |
48 | ```js
49 | var request = require('request');
50 | var file = request("https://www.google.ca/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png");
51 |
52 | var data = {
53 | from: 'Excited User ',
54 | to: 'serobnic@mail.ru',
55 | subject: 'Hello',
56 | text: 'Testing some Mailgun awesomeness!',
57 | attachment: file
58 | };
59 |
60 | mailgun.messages().send(data, function (error, body) {
61 | console.log(body);
62 | });
63 | ```
64 |
65 | Finally we provide a `Mailgun.Attachment` class to add attachments with a bit more customization. The Attachment
66 | constructor takes an `options` object. The `options` parameters can have the following fields:
67 | * `data` - can be one of
68 | * a string representing file path to the attachment
69 | * a buffer of file data
70 | * an instance of `Stream` which means it is a readable stream.
71 | * `filename` - the file name to be used for the attachment. Default is 'file'
72 | * `contentType` - the content type. Required for case of `Stream` data. Ex. `image/jpeg`.
73 | * `knownLength` - the content length in bytes. Required for case of `Stream` data.
74 |
75 | If an attachment object does not satisfy those valid conditions it is ignored. Multiple attachments can be sent by
76 | passing an array in the `attachment` parameter. The array elements can be of any one of the valid types and each one
77 | will be handled appropriately.
78 |
79 | ```js
80 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
81 | var filename = 'mailgun_logo.png';
82 | var filepath = path.join(__dirname, filename);
83 | var file = fs.readFileSync(filepath);
84 |
85 | var attch = new mailgun.Attachment({data: file, filename: filename});
86 |
87 | var data = {
88 | from: 'Excited User ',
89 | to: 'serobnic@mail.ru',
90 | subject: 'Hello',
91 | text: 'Testing some Mailgun awesomeness!',
92 | attachment: attch
93 | };
94 |
95 | mailgun.messages().send(data, function (error, body) {
96 | console.log(body);
97 | });
98 | ```
99 |
100 | ```js
101 | var mailgun = require('mailgun-js')({apiKey: api_key, domain: domain});
102 | var filename = 'mailgun_logo.png';
103 | var filepath = path.join(__dirname, filename);
104 | var fileStream = fs.createReadStream(filepath);
105 | var fileStat = fs.statSync(filepath);
106 |
107 | msg.attachment = new mailgun.Attachment({
108 | data: fileStream,
109 | filename: 'my_custom_name.png',
110 | knownLength: fileStat.size,
111 | contentType: 'image/png'});
112 |
113 | mailgun.messages().send(data, function (error, body) {
114 | console.log(body);
115 | });
116 | ```
117 |
118 | #### Sending MIME messages
119 |
120 | Sending messages in MIME format can be accomplished using the `sendMime()` function of the `messages()` proxy object.
121 | The `data` parameter for the function has to have `to` and `message` properties. The `message` property can be a full
122 | file path to the MIME file, a stream of the file (that is a `Stream` object), or a string representation of the MIME
123 | message. To build a MIME string you can use the [nodemailer](https://www.npmjs.org/package/nodemailer) library.
124 | Some examples:
125 |
126 | ```js
127 | var domain = 'www.mydomain.com';
128 | var mailgun = require('mailgun-js')({ apiKey: "YOUR API KEY", domain: domain });
129 | var MailComposer = require('nodemailer/lib/mail-composer');
130 |
131 | var mailOptions = {
132 | from: 'you@samples.mailgun.org',
133 | to: 'mm@samples.mailgun.org',
134 | subject: 'Test email subject',
135 | text: 'Test email text'
136 | };
137 |
138 | var mail = new MailComposer(mailOptions);
139 |
140 | mail.compile().build((err, message) => {
141 | var dataToSend = {
142 | to: 'mm@samples.mailgun.org',
143 | message: message.toString('ascii')
144 | };
145 |
146 | mailgun.messages().sendMime(dataToSend, (err, body) => {
147 | if (err) {
148 | console.log(err);
149 | return;
150 | }
151 |
152 | console.log(body);
153 | });
154 | });
155 | ```
156 | #### Referencing MIME file
157 |
158 | ```js
159 | var filepath = '/path/to/message.mime';
160 |
161 | var data = {
162 | to: fixture.message.to,
163 | message: filepath
164 | };
165 |
166 | mailgun.messages().sendMime(data, function (err, body) {
167 | console.log(body);
168 | });
169 | ```
170 |
171 | ```js
172 | var filepath = '/path/to/message.mime';
173 |
174 | var data = {
175 | to: fixture.message.to,
176 | message: fs.createReadStream(filepath)
177 | };
178 |
179 | mailgun.messages().sendMime(data, function (err, body) {
180 | console.log(body);
181 | });
182 | ```
183 |
--------------------------------------------------------------------------------
/lib/mailgun.js:
--------------------------------------------------------------------------------
1 | const tsscmp = require('tsscmp')
2 | const crypto = require('crypto')
3 |
4 | const Attachment = require('./attachment')
5 | const Request = require('./request')
6 | const builder = require('./build')
7 | const resources = require('./schema').definitions
8 |
9 | const mailgunExpirey = 15 * 60 * 1000
10 | const mailgunHashType = 'sha256'
11 | const mailgunSignatureEncoding = 'hex'
12 |
13 | class Mailgun {
14 | constructor (options) {
15 | if (!options.apiKey) {
16 | throw new Error('apiKey value must be defined!')
17 | }
18 | this.username = 'api'
19 | this.apiKey = options.apiKey
20 | this.publicApiKey = options.publicApiKey
21 | this.domain = options.domain
22 | this.auth = [this.username, this.apiKey].join(':')
23 | this.mute = options.mute || false
24 | this.timeout = options.timeout
25 |
26 | this.host = options.host || 'api.mailgun.net'
27 | this.endpoint = options.endpoint || '/v3'
28 | this.protocol = options.protocol || 'https:'
29 | this.port = options.port || 443
30 | this.retry = options.retry || 1
31 |
32 | this.testMode = options.testMode
33 | this.testModeLogger = options.testModeLogger
34 |
35 | if (options.proxy) {
36 | this.proxy = options.proxy
37 | }
38 |
39 | this.options = {
40 | host: this.host,
41 | endpoint: this.endpoint,
42 | protocol: this.protocol,
43 | port: this.port,
44 | auth: this.auth,
45 | proxy: this.proxy,
46 | timeout: this.timeout,
47 | retry: this.retry,
48 | testMode: this.testMode,
49 | testModeLogger: this.testModeLogger
50 | }
51 |
52 | this.mailgunTokens = {}
53 | }
54 |
55 | getDomain (method, resource) {
56 | let d = this.domain
57 |
58 | // filter out API calls that do not require a domain specified
59 | if ((resource.indexOf('/routes') >= 0) ||
60 | (resource.indexOf('/lists') >= 0) ||
61 | (resource.indexOf('/address') >= 0) ||
62 | (resource.indexOf('/domains') >= 0)) {
63 | d = ''
64 | } else if ((resource.indexOf('/messages') >= 0) &&
65 | (method === 'GET' || method === 'DELETE')) {
66 | d = `domains/${this.domain}`
67 | }
68 |
69 | return d
70 | }
71 |
72 | getRequestOptions (resource) {
73 | let o = this.options
74 |
75 | // use public API key if we have it for the routes that require it
76 | if ((resource.indexOf('/address/validate') >= 0 ||
77 | (resource.indexOf('/address/parse') >= 0)) &&
78 | this.publicApiKey) {
79 | const copy = Object.assign({}, this.options)
80 |
81 | copy.auth = [this.username, this.publicApiKey].join(':')
82 | o = copy
83 | }
84 |
85 | return o
86 | }
87 |
88 | request (method, resource, data, fn) {
89 | let fullpath = resource
90 | const domain = this.getDomain(method, resource)
91 |
92 | if (domain) {
93 | fullpath = '/'.concat(domain, resource)
94 | }
95 |
96 | const req = new Request(this.options)
97 |
98 | return req.request(method, fullpath, data, fn)
99 | }
100 |
101 | post (path, data, fn) {
102 | const req = new Request(this.options)
103 |
104 | return req.request('POST', path, data, fn)
105 | }
106 |
107 | get (path, data, fn) {
108 | const req = new Request(this.options)
109 |
110 | return req.request('GET', path, data, fn)
111 | }
112 |
113 | delete (path, data, fn) {
114 | const req = new Request(this.options)
115 |
116 | return req.request('DELETE', path, data, fn)
117 | }
118 |
119 | put (path, data, fn) {
120 | const req = new Request(this.options)
121 |
122 | return req.request('PUT', path, data, fn)
123 | }
124 |
125 | validateWebhook (timestamp, token, signature) {
126 | const adjustedTimestamp = parseInt(timestamp, 10) * 1000
127 | const fresh = (Math.abs(Date.now() - adjustedTimestamp) < mailgunExpirey)
128 |
129 | if (!fresh) {
130 | if (!this.mute) {
131 | console.error('[mailgun] Stale Timestamp: this may be an attack')
132 | console.error('[mailgun] However, this is most likely your fault\n')
133 | console.error('[mailgun] run `ntpdate ntp.ubuntu.com` and check your system clock\n')
134 | console.error(`[mailgun] System Time: ${new Date().toString()}`)
135 | console.error(`[mailgun] Mailgun Time: ${new Date(adjustedTimestamp).toString()}`, timestamp)
136 | console.error(`[mailgun] Delta: ${Date.now() - adjustedTimestamp}`)
137 | }
138 |
139 | return false
140 | }
141 |
142 | if (this.mailgunTokens[token]) {
143 | if (!this.mute) {
144 | console.error('[mailgun] Replay Attack')
145 | }
146 |
147 | return false
148 | }
149 |
150 | this.mailgunTokens[token] = true
151 |
152 | const tokenTimeout = setTimeout(() => {
153 | delete this.mailgunTokens[token]
154 | }, mailgunExpirey + (5 * 1000))
155 |
156 | tokenTimeout.unref()
157 |
158 | return tsscmp(
159 | signature, crypto.createHmac(mailgunHashType, this.apiKey)
160 | .update(Buffer.from(timestamp + token, 'utf-8'))
161 | .digest(mailgunSignatureEncoding)
162 | )
163 | }
164 |
165 | validate (address, isPrivate, opts, fn) {
166 | if (typeof opts === 'function') {
167 | fn = opts
168 | opts = {}
169 | }
170 |
171 | if (typeof isPrivate === 'object') {
172 | opts = isPrivate
173 | isPrivate = false
174 | }
175 |
176 | if (typeof isPrivate === 'function') {
177 | fn = isPrivate
178 | isPrivate = false
179 | opts = {}
180 | }
181 |
182 | let resource = '/address/validate'
183 |
184 | if (isPrivate) {
185 | resource = '/address/private/validate'
186 | }
187 |
188 | const options = this.getRequestOptions(resource)
189 |
190 | const req = new Request(options)
191 | const data = Object.assign({}, {
192 | address
193 | }, opts)
194 |
195 | return req.request('GET', resource, data, fn)
196 | }
197 |
198 | parse (addresses, isPrivate, opts, fn) {
199 | if (typeof opts === 'function') {
200 | fn = opts
201 | opts = {}
202 | }
203 |
204 | if (typeof isPrivate === 'object') {
205 | opts = isPrivate
206 | isPrivate = false
207 | }
208 |
209 | if (typeof isPrivate === 'function') {
210 | fn = isPrivate
211 | isPrivate = false
212 | opts = {}
213 | }
214 |
215 | let resource = '/address/parse'
216 |
217 | if (isPrivate) {
218 | resource = '/address/private/parse'
219 | }
220 |
221 | const options = this.getRequestOptions(resource)
222 |
223 | const req = new Request(options)
224 | const data = Object.assign({}, {
225 | addresses
226 | }, opts)
227 |
228 | return req.request('GET', resource, data, fn)
229 | }
230 | }
231 |
232 | builder.build(Mailgun, resources)
233 |
234 | Mailgun.prototype.Attachment = Attachment
235 |
236 | Mailgun.prototype.Mailgun = Mailgun
237 |
238 | function create (options) {
239 | return new Mailgun(options)
240 | }
241 |
242 | module.exports = create
243 |
--------------------------------------------------------------------------------
/test/domains.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | describe('Domains', () => {
11 | beforeEach((done) => {
12 | setTimeout(done, 500)
13 | })
14 |
15 | it('test domains().create() invalid missing address', (done) => {
16 | mailgun.domains().create({}, (err) => {
17 | assert.ok(err)
18 | assert(/Missing parameter 'name'/.test(err.message))
19 | done()
20 | })
21 | })
22 |
23 | it.skip('test domains().create() ', () => {
24 | // mailgun.domains().create(fixture.new_domain, function (err, body) {
25 | // assert.ifError(err);
26 | // assert.ok(body.message);
27 | // assert(/Domain has been created/.test(body.message));
28 | // assert.ok(body.domain);
29 | // assert.strictEqual(fixture.new_domain.name, body.domain.name);
30 | // assert.strictEqual(fixture.new_domain.smtp_password, body.domain.smtp_password);
31 | // done();
32 | // });
33 | })
34 |
35 | it('test domains().list()', (done) => {
36 | mailgun.domains().list((err, body) => {
37 | assert.ifError(err)
38 | assert.ok(body.total_count)
39 | assert.ok(body.items)
40 | done()
41 | })
42 | })
43 |
44 | it('test domains().info()', (done) => {
45 | mailgun.domains(fixture.existing_domain.name).info((err, body) => {
46 | assert.ifError(err)
47 | assert.ok(body.domain)
48 | assert.strictEqual(fixture.existing_domain.name, body.domain.name)
49 | done()
50 | })
51 | })
52 |
53 | it('test domains().credentials().list()', (done) => {
54 | mailgun.domains(fixture.existing_domain.name).credentials().list((err, body) => {
55 | assert.ifError(err)
56 | assert.ok(body.total_count)
57 | assert.ok(body.items)
58 | done()
59 | })
60 | })
61 |
62 | it('test domains().credentials().create() missing login', (done) => {
63 | mailgun.domains(fixture.existing_domain.name).credentials().create({}, (err) => {
64 | assert.ok(err)
65 | assert(/Missing parameter 'login'/.test(err.message))
66 | done()
67 | })
68 | })
69 |
70 | it('test domains().credentials().create() missing password', (done) => {
71 | mailgun.domains(fixture.existing_domain.name).credentials().create({
72 | 'login': fixture.credentials.login
73 | }, (err) => {
74 | assert.ok(err)
75 | assert(/Missing parameter 'password'/.test(err.message))
76 | done()
77 | })
78 | })
79 |
80 | it('test domains().credentials().create() invalid login type', (done) => {
81 | mailgun.domains(fixture.existing_domain.name).credentials().create({
82 | 'login': 123,
83 | 'password': 'password'
84 | }, (err) => {
85 | assert.ok(err)
86 | assert(/Invalid parameter type./.test(err.message))
87 | done()
88 | })
89 | })
90 |
91 | it('test domains().credentials().create() invalid password type', (done) => {
92 | mailgun.domains(fixture.existing_domain.name).credentials().create({
93 | 'login': fixture.credentials.login,
94 | 'password': 123
95 | }, (err) => {
96 | assert.ok(err)
97 | assert(/Invalid parameter type./.test(err.message))
98 | done()
99 | })
100 | })
101 |
102 | it('test domains().credentials().create()', (done) => {
103 | mailgun.domains(fixture.existing_domain.name).credentials().create({
104 | 'login': fixture.credentials.login,
105 | 'password': fixture.credentials.password
106 | }, (err, body) => {
107 | assert.ifError(err)
108 | assert.ok(body.message)
109 | done()
110 | })
111 | })
112 |
113 | it('test domains().credentials().update() missing password', (done) => {
114 | mailgun.domains(fixture.existing_domain.name).credentials(fixture.credentials.login).update({}, (err) => {
115 | assert.ok(err)
116 | assert(/Missing parameter 'password'/.test(err.message))
117 | done()
118 | })
119 | })
120 |
121 | it('test domains().credentials().update() invalid password type', (done) => {
122 | mailgun.domains(fixture.existing_domain.name).credentials(fixture.credentials.login).update({
123 | 'password': 123
124 | }, (err) => {
125 | assert.ok(err)
126 | assert(/Invalid parameter type./.test(err.message))
127 | done()
128 | })
129 | })
130 |
131 | it('test domains().credentials().update()', (done) => {
132 | mailgun.domains(fixture.existing_domain.name).credentials(fixture.credentials.login).update({
133 | 'password': fixture.credentials.password
134 | }, (err, body) => {
135 | assert.ifError(err)
136 | assert.ok(body.message)
137 | done()
138 | })
139 | })
140 |
141 | it('test domains().credentials().delete()', (done) => {
142 | mailgun.domains(fixture.existing_domain.name).credentials(fixture.credentials.login).delete((err, body) => {
143 | assert.ifError(err)
144 | assert.ok(body.message)
145 | done()
146 | })
147 | })
148 |
149 | it.skip('test domains().delete()', () => {
150 | // var domain = fixture.new_domain.name;
151 | // mailgun.domains(domain).delete(function (err, body) {
152 | // assert.ifError(err);
153 | // assert.ok(body.message);
154 | // assert(/Domain has been deleted/.test(body.message));
155 | // done();
156 | // });
157 | })
158 |
159 | it('test domains().verify() that it is a function', () => {
160 | // we can't actally just call this endpoint
161 | const fn = mailgun.domains(fixture.existing_domain.name).verify
162 |
163 | assert.ok(typeof fn === 'function')
164 | })
165 |
166 | it('server error should have status code', (done) => {
167 | mailgun.domains('domaintest@test123.com').info((err) => {
168 | assert.ok(err)
169 | assert.ok(err.statusCode)
170 | assert.strictEqual(404, err.statusCode)
171 | done()
172 | })
173 | })
174 |
175 | it('test domains().tracking().into()', (done) => {
176 | mailgun.domains(fixture.existing_domain.name).tracking().info((err, body) => {
177 | assert.ifError(err)
178 | assert.ok(body)
179 | assert.ok(body.tracking)
180 | done()
181 | })
182 | })
183 |
184 | it('test domains().tracking().open().update()', (done) => {
185 | mailgun.domains(fixture.existing_domain.name).tracking().open().update({ active: 'no' }, (err, body) => {
186 | assert.ifError(err)
187 | assert.ok(body)
188 | assert.ok(body.open)
189 | done()
190 | })
191 | })
192 |
193 | it('test domains().tracking().click().update()', (done) => {
194 | mailgun.domains(fixture.existing_domain.name).tracking().click().update({ active: false }, (err, body) => {
195 | assert.ifError(err)
196 | assert.ok(body)
197 | assert.ok(body.click)
198 | done()
199 | })
200 | })
201 |
202 | it('test domains().tracking().unsubscribe().update()', (done) => {
203 | const data = {
204 | active: false,
205 | html_footer: 'text
',
206 | text_footer: 'text'
207 | }
208 |
209 | mailgun.domains(fixture.existing_domain.name).tracking().unsubscribe().update(data, (err, body) => {
210 | assert.ifError(err)
211 | assert.ok(body)
212 | assert.ok(body.unsubscribe)
213 | done()
214 | })
215 | })
216 | })
217 |
--------------------------------------------------------------------------------
/bin/docs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var assert = require('assert'),
4 | fs = require('fs'),
5 | Mailgun = require('../'),
6 | mailgun = new Mailgun({apiKey: 'asdf'}),
7 | inflection = require('inflection'),
8 | path = require('path'),
9 | resources = require('../lib/schema').definitions,
10 | resource,
11 | resourceName;
12 |
13 | for (resourceName in resources) {
14 | resource = resources[resourceName];
15 | createDocumentation(resourceName, resource);
16 | }
17 |
18 | function createDocumentation (resourceName, resource) {
19 | var file = createFile(resourceName);
20 |
21 | process.stdout.write('\nBuilding docs for ' + resourceName + ':\n');
22 |
23 | fs.appendFileSync(file, '# ' + resourceName + '\n\n');
24 | if (resource.description) fs.appendFileSync(file, resource.description + '\n\n');
25 |
26 | addActions(file, resource.links);
27 | addAttributes(file, resource.attributes);
28 | }
29 |
30 | function getName(name) {
31 | name = name.toLowerCase();
32 | name = inflection.dasherize(name).replace(/-/g, '_');
33 | name = inflection.camelize(name, true);
34 |
35 | return name;
36 | }
37 |
38 | function createFile (resourceName) {
39 | var fileName = getName(resourceName),
40 | file = path.join(__dirname, '../docs/api/' + fileName + '.md');
41 |
42 | fs.writeFileSync(file, '');
43 |
44 | return file;
45 | }
46 |
47 | function addActions (file, actions) {
48 | var action, actionName, i;
49 |
50 | process.stdout.write(' Building actions:');
51 | fs.appendFileSync(file, '## Actions\n\n');
52 |
53 | for (i in actions) {
54 | action = actions[i];
55 | actionName = action.title;
56 |
57 | process.stdout.write('\n ' + actionName);
58 | addAction(file, actionName, action);
59 | }
60 | }
61 |
62 | function addAction (file, actionName, action) {
63 |
64 | fs.appendFileSync(file, '### `' + getName(actionName) + '`\n\n');
65 | if (resource.description) fs.appendFileSync(file, action.description + '\n\n');
66 | fs.appendFileSync(file, '`' + getFunctionCall(actionName, action) + '`\n\n');
67 | fs.appendFileSync(file, 'Method | ');
68 | fs.appendFileSync(file, 'Path\n');
69 | fs.appendFileSync(file, '--- | ');
70 | fs.appendFileSync(file, '---\n');
71 | fs.appendFileSync(file, action.method + ' | ');
72 |
73 | // HACK append the domain to href
74 | var realHref = appendDomainToAction(action.method, action.href);
75 |
76 | fs.appendFileSync(file, prettifyHref(realHref) + '\n\n');
77 | addActionAttributes(file, action);
78 |
79 | checkAction(actionName, action);
80 | }
81 |
82 | function appendDomainToAction(method, href) {
83 | var d = '/{domain}';
84 |
85 | //filter out API calls that do not require a domain specified
86 | if ((href.indexOf('/routes') >= 0)
87 | || (href.indexOf('/lists') >= 0)
88 | || (href.indexOf('/address') >= 0)
89 | || (href.indexOf('/domains') >= 0 )) {
90 | d = '';
91 | }
92 | else if ((href.indexOf('/messages') >= 0)
93 | && (method === 'GET' || method === 'DELETE')) {
94 | d = '/domains/{domain}';
95 | }
96 |
97 | return d.concat(href);
98 | }
99 |
100 | function getFunctionCall (actionName, action) {
101 | // HACKY special case for members bulk add
102 | var actionPath = action.href.replace(/\.json/gi,'');
103 | var actionPath = actionPath.replace(/\.mime/gi,'');
104 |
105 | var path = actionPath.split(/\//),
106 | segments = path.slice(1, path.length),
107 | functionCall = 'mailgun',
108 | i,
109 | segment;
110 |
111 | for (i = 0; i < segments.length; i ++) {
112 | segment = segments[i];
113 |
114 | if (segment.match(/{[^}]+}/)) {
115 | continue;
116 | } else {
117 | functionCall += '.' + getName(segment);
118 |
119 | if (segments[i + 1] && segments[i + 1].match(/{[^}]+}/)) {
120 | functionCall += '(' + prettifySegment(segments[i + 1]) + ')';
121 | } else {
122 | functionCall += '()';
123 | }
124 | }
125 | }
126 |
127 | functionCall += '.' + getName(actionName) + '(';
128 |
129 | if (['PATCH', 'POST', 'PUT'].indexOf(action.method) > -1) {
130 | functionCall += '{attributes}, ';
131 | }
132 |
133 | functionCall += '{callback});';
134 |
135 | return functionCall;
136 | }
137 |
138 | function prettifyHref (href) {
139 | var segments = href.split(/\//);
140 | segments = segments.slice(1, segments.length);
141 | segments = segments.map(prettifySegment)
142 | href = '/' + segments.join('/');
143 | return href;
144 | }
145 |
146 | function prettifySegment (segment) {
147 | var unescaped = unescape(segment),
148 | identities;
149 |
150 | if (unescaped === segment) {
151 | return segment;
152 | }
153 |
154 | identities = getIdentitiesFromParam(unescaped);
155 | return '{' + identities[0] + '_' + identities.slice(1, identities.length).sort().join('_or_') + '}';
156 | }
157 |
158 | function getIdentitiesFromParam(param) {
159 | var identities = param.replace(/[{}()#]/g, '').split("/"),
160 | resourceName;
161 |
162 | identities = identities.slice(1, identities.length);
163 | identities = resources[identities[1]].definitions.identity.anyOf;
164 |
165 | identities = identities.map(function (identity) {
166 | var parts = identity.$ref.split('/');
167 | resourceName = parts[2];
168 | return parts.slice(-1)[0]
169 | });
170 |
171 | identities.unshift(resourceName);
172 |
173 | return identities;
174 | }
175 |
176 | function addActionAttributes (file, action) {
177 | if (!action.attributes) return;
178 |
179 | addActionAttributeGroup(file, action.attributes.optional, 'Optional');
180 | fs.appendFileSync(file, '\n');
181 |
182 | addActionAttributeGroup(file, action.attributes.required, 'Required');
183 | fs.appendFileSync(file, '\n');
184 | }
185 |
186 | function addActionAttributeGroup (file, attributes, type) {
187 | if (!attributes) return;
188 |
189 | fs.appendFileSync(file, '#### ' + type + ' Attributes\n\n');
190 |
191 | attributes.forEach(function (attribute) {
192 | fs.appendFileSync(file, '- ' + attribute + '\n');
193 | });
194 | }
195 |
196 | function addAttributes (file, attributes) {
197 | var attributeName, attribute;
198 |
199 | if (!attributes) return;
200 |
201 | process.stdout.write('\n Building attributes\n');
202 | fs.appendFileSync(file, '## Attributes\n\n');
203 |
204 | for (attributeName in attributes) {
205 | attribute = attributes[attributeName];
206 | addAttribute(file, attributeName, attribute);
207 | }
208 | }
209 |
210 | function addAttribute (file, attributeName, attribute) {
211 | fs.appendFileSync(file, '### `' + attributeName + '`\n\n');
212 |
213 | fs.appendFileSync(file, '*' + attribute.description + '*\n\n');
214 |
215 | fs.appendFileSync(file, 'Example | ');
216 | fs.appendFileSync(file, 'Serialized? | ');
217 | fs.appendFileSync(file, 'Type\n');
218 | fs.appendFileSync(file, '--- | ');
219 | fs.appendFileSync(file, '--- | ');
220 | fs.appendFileSync(file, '---\n');
221 | fs.appendFileSync(file, '`' + attribute.example + '` | ');
222 | fs.appendFileSync(file, attribute.serialized + ' | ');
223 | fs.appendFileSync(file, attribute.type + '\n\n');
224 | }
225 |
226 | function checkAction (actionName, action) {
227 | var functionToTest = getFunctionCall(actionName, action).replace(/{.*}/g, '').slice(0, -3)
228 | assert.strictEqual(eval('typeof ' + functionToTest), 'function', functionToTest + ' is not a Function');
229 | process.stdout.write(' ' + String.fromCharCode(0x2713));
230 | }
231 |
--------------------------------------------------------------------------------
/test/mailinglists.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const assert = require('assert')
4 |
5 | const mailgun = require('../')({
6 | 'apiKey': auth.api_key,
7 | 'domain': auth.domain
8 | })
9 |
10 | describe('Mailing lists', () => {
11 | beforeEach((done) => {
12 | setTimeout(done, 500)
13 | })
14 |
15 | it('test lists().create() invalid missing address', (done) => {
16 | mailgun.lists().create({}, (err) => {
17 | assert.ok(err)
18 | assert(/Missing parameter 'address'/.test(err.message))
19 | done()
20 | })
21 | })
22 |
23 | it('test lists.create()', (done) => {
24 | mailgun.lists().create(fixture.mailingList, (err, body) => {
25 | assert.ifError(err)
26 | assert.ok(body.message)
27 | assert.ok(body.list)
28 | assert.strictEqual(fixture.mailingList.address, body.list.address)
29 | assert.strictEqual(fixture.mailingList.name, body.list.name)
30 | assert.strictEqual(fixture.mailingList.description, body.list.description)
31 | done()
32 | })
33 | })
34 |
35 | it('test lists.list()', (done) => {
36 | mailgun.lists().list((err, body) => {
37 | assert.ifError(err)
38 | assert.ok(body.total_count)
39 | assert.ok(body.items)
40 | done()
41 | })
42 | })
43 |
44 | it('test lists().info()', (done) => {
45 | mailgun.lists(fixture.mailingList.address).info((err, body) => {
46 | assert.ifError(err)
47 | assert.ok(body.list)
48 | assert.ok(body.list.address)
49 | assert.ok(body.list.name)
50 | done()
51 | })
52 | })
53 |
54 | it('test lists().update()', (done) => {
55 | const name = 'List Updated'
56 | const desc = 'My updated test mailing list'
57 |
58 | mailgun.lists(fixture.mailingList.address).update({
59 | name,
60 | 'description': desc
61 | }, (err, body) => {
62 | assert.ifError(err)
63 | assert.ok(body.message)
64 | assert.strictEqual(fixture.mailingList.address, body.list.address)
65 | assert.strictEqual(name, body.list.name)
66 | assert.strictEqual(desc, body.list.description)
67 | done()
68 | })
69 | })
70 |
71 | it('test lists().members().create() no member address', (done) => {
72 | mailgun.lists(fixture.mailingList.address).members().create({}, (err) => {
73 | assert.ok(err)
74 | assert(/Missing parameter 'address'/.test(err.message))
75 | done()
76 | })
77 | })
78 |
79 | it('test lists().members().create() invalid address type', (done) => {
80 | mailgun.lists(fixture.mailingList.address).members().create({
81 | 'address': 1234
82 | }, (err) => {
83 | assert.ok(err)
84 | assert(/Invalid parameter type./.test(err.message))
85 | done()
86 | })
87 | })
88 |
89 | it('test lists().members().create()', (done) => {
90 | const data = {
91 | 'subscribed': true,
92 | 'address': 'bob@gmail.com',
93 | 'name': 'Bob Bar',
94 | 'vars': {
95 | 'age': 26
96 | }
97 | }
98 |
99 | mailgun.lists(fixture.mailingList.address).members().create(data, (err, body) => {
100 | assert.ifError(err)
101 | assert.ok(body.message)
102 | assert.ok(body.member)
103 | assert.ok(body.member.address)
104 | assert.ok(body.member.name)
105 | assert.ok(body.member.vars)
106 | assert.strictEqual(data.subscribed, body.member.subscribed)
107 | assert.strictEqual(data.address, body.member.address)
108 | assert.strictEqual(data.name, body.member.name)
109 | assert.strictEqual(data.vars.age, body.member.vars.age)
110 | done()
111 | })
112 | })
113 |
114 | it('test lists().members().list()', (done) => {
115 | mailgun.lists(fixture.mailingList.address).members().list((err, body) => {
116 | assert.ifError(err)
117 | assert.ok(body.total_count >= 0)
118 | assert.ok(body.items)
119 | done()
120 | })
121 | })
122 |
123 | it('test lists().members().pages().page()', (done) => {
124 | mailgun.lists(fixture.mailingList.address).members().pages().page({
125 | 'page': 'first'
126 | }, (err, body) => {
127 | assert.ifError(err)
128 | assert.ok(Array.isArray(body.items))
129 | assert.ok(typeof body.paging === 'object')
130 | done()
131 | })
132 | })
133 |
134 | it('test lists.members().info()', (done) => {
135 | const data = {
136 | 'subscribed': true,
137 | 'address': 'bob@gmail.com',
138 | 'name': 'Bob Bar',
139 | 'vars': {
140 | 'age': 26
141 | }
142 | }
143 |
144 | mailgun.lists(fixture.mailingList.address).members('bob@gmail.com').info((err, body) => {
145 | assert.ifError(err)
146 | assert.ok(body.member)
147 | assert.ok(body.member.vars)
148 | assert.strictEqual(data.subscribed, body.member.subscribed)
149 | assert.strictEqual(data.address, body.member.address)
150 | assert.strictEqual(data.name, body.member.name)
151 | assert.strictEqual(data.vars.age, body.member.vars.age)
152 | done()
153 | })
154 | })
155 |
156 | it('test lists().members().update()', (done) => {
157 | const data = {
158 | 'subscribed': false,
159 | 'address': 'foo@gmail.com',
160 | 'name': 'Foo Bar',
161 | 'vars': {
162 | 'age': 36
163 | }
164 | }
165 |
166 | mailgun.lists(fixture.mailingList.address).members('bob@gmail.com').update(data, (err, body) => {
167 | assert.ifError(err)
168 | assert.ok(body.message)
169 | assert.ok(body.member)
170 | assert.ok(body.member.vars)
171 | assert.strictEqual(data.subscribed, body.member.subscribed)
172 | assert.strictEqual(data.address, body.member.address)
173 | assert.strictEqual(data.name, body.member.name)
174 | assert.strictEqual(data.vars.age, body.member.vars.age)
175 | done()
176 | })
177 | })
178 |
179 | it('test lists().members().delete()', (done) => {
180 | const address = 'foo@gmail.com'
181 |
182 | mailgun.lists(fixture.mailingList.address).members(address).delete((err, body) => {
183 | assert.ifError(err)
184 | assert.ok(body.message)
185 | done()
186 | })
187 | })
188 |
189 | it('test lists().members().add() without upsert', (done) => {
190 | const members = [{
191 | 'address': 'Alice ',
192 | 'vars': {
193 | 'age': 26
194 | }
195 | },
196 | {
197 | 'name': 'Bob',
198 | 'address': 'bob@example.com',
199 | 'vars': {
200 | 'age': 34
201 | }
202 | }
203 | ]
204 |
205 | mailgun.lists(fixture.mailingList.address).members().add({
206 | members
207 | }, (err, body) => {
208 | assert.ifError(err)
209 | assert.ok(body.list)
210 | assert.ok(body.list.members_count >= 0)
211 | done()
212 | })
213 | })
214 |
215 | it('test lists().members().add()', (done) => {
216 | const members = [{
217 | 'address': 'Alice ',
218 | 'vars': {
219 | 'age': 26
220 | }
221 | },
222 | {
223 | 'name': 'Bob',
224 | 'address': 'bob@example.com',
225 | 'vars': {
226 | 'age': 34
227 | }
228 | }
229 | ]
230 |
231 | mailgun.lists(fixture.mailingList.address).members().add({
232 | members,
233 | 'upsert': true
234 | }, (err, body) => {
235 | assert.ifError(err)
236 | assert.ok(body.list)
237 | assert.ok(body.list.members_count >= 0)
238 | done()
239 | })
240 | })
241 |
242 | it('test lists().delete()', (done) => {
243 | mailgun.lists(fixture.mailingList.address).delete((err, body) => {
244 | assert.ifError(err)
245 | assert.ok(body)
246 | assert.ok(body.message)
247 | done()
248 | })
249 | })
250 | })
251 |
--------------------------------------------------------------------------------
/lib/request.js:
--------------------------------------------------------------------------------
1 | const https = require('https')
2 | const http = require('http')
3 | const ProxyAgent = require('proxy-agent')
4 | const qs = require('querystring')
5 | const fs = require('fs')
6 | const Readable = require('stream').Readable
7 | const FormData = require('form-data')
8 | const Attachment = require('./attachment')
9 | const retry = require('async').retry
10 | const promisifyCall = require('promisify-call')
11 |
12 | const debug = require('debug')('mailgun-js')
13 |
14 | function isOk (i) {
15 | return typeof i !== 'undefined' && i !== null
16 | }
17 |
18 | function getDataValue (key, input) {
19 | if (isSpecialParam(key) && (typeof input === 'object')) {
20 | return JSON.stringify(input)
21 | } else if (typeof input === 'number' || typeof input === 'boolean') {
22 | return input.toString()
23 | }
24 |
25 | return input
26 | }
27 |
28 | function isSpecialParam (paramKey) {
29 | const key = paramKey.toLowerCase()
30 |
31 | return ((key === 'vars' || key === 'members' || key === 'recipient-variables') || (key.indexOf('v:') === 0))
32 | }
33 |
34 | function isMultiUnsubsribe (path, data) {
35 | return path.indexOf('/unsubscribes') && data && Array.isArray(data)
36 | }
37 |
38 | function prepareData (data) {
39 | const params = {}
40 |
41 | for (const key in data) {
42 | if (key !== 'attachment' && key !== 'inline' && isOk(data[key])) {
43 | const value = getDataValue(key, data[key])
44 |
45 | if (isOk(value)) {
46 | params[key] = value
47 | }
48 | } else {
49 | params[key] = data[key]
50 | }
51 | }
52 |
53 | return params
54 | }
55 |
56 | class Request {
57 | constructor (options) {
58 | this.host = options.host
59 | this.protocol = options.protocol
60 | this.port = options.port
61 | this.endpoint = options.endpoint
62 | this.auth = options.auth
63 | this.proxy = options.proxy
64 | this.timeout = options.timeout
65 | this.retry = options.retry || 1
66 | this.testMode = options.testMode
67 | this.testModeLogger = typeof options.testModeLogger === 'function'
68 | ? options.testModeLogger : this.defaultTestModeLogger
69 | }
70 |
71 | _request (method, resource, data, fn) {
72 | let path = ''.concat(this.endpoint, resource)
73 |
74 | const params = prepareData(data)
75 |
76 | this.payload = ''
77 |
78 | const isMIME = path.indexOf('/messages.mime') >= 0
79 |
80 | this.headers = {}
81 | if (method === 'GET' || method === 'DELETE') {
82 | this.payload = qs.stringify(params)
83 | if (this.payload) path = path.concat('?', this.payload)
84 | } else {
85 | if (isMIME) {
86 | this.headers['Content-Type'] = 'multipart/form-data'
87 | } else if (method === 'POST' && isMultiUnsubsribe(path, data)) {
88 | this.headers['Content-Type'] = 'application/json'
89 | } else {
90 | this.headers['Content-Type'] = 'application/x-www-form-urlencoded'
91 | }
92 |
93 | if (params && (params.attachment || params.inline || (isMIME && params.message))) {
94 | this.prepareFormData(params)
95 | } else {
96 | if (method === 'POST' && isMultiUnsubsribe(path, data)) {
97 | this.payload = JSON.stringify(data)
98 | } else {
99 | this.payload = qs.stringify(params)
100 | }
101 |
102 | if (this.payload) {
103 | this.headers['Content-Length'] = Buffer.byteLength(this.payload)
104 | } else {
105 | this.headers['Content-Length'] = 0
106 | }
107 | }
108 | }
109 |
110 | // check for MIME is true in case of messages GET
111 | if (method === 'GET' &&
112 | path.indexOf('/messages') >= 0 &&
113 | params && params.MIME === true) {
114 | this.headers.Accept = 'message/rfc2822'
115 | }
116 |
117 | debug('%s %s', method, path)
118 |
119 | const opts = {
120 | 'hostname': this.host,
121 | 'port': this.port,
122 | 'protocol': this.protocol,
123 | path,
124 | method,
125 | 'headers': this.headers,
126 | 'auth': this.auth,
127 | 'agent': false,
128 | 'timeout': this.timeout
129 | }
130 |
131 | if (this.proxy) {
132 | opts.agent = new ProxyAgent(this.proxy)
133 | }
134 |
135 | if (this.testMode) {
136 | this.testModeLogger(opts, this.payload, this.form)
137 | return fn()
138 | }
139 |
140 | if (typeof this.retry === 'object' || this.retry > 1) {
141 | retry(this.retry, (retryCb) => {
142 | this.callback = retryCb
143 | this.performRequest(opts)
144 | }, fn)
145 | } else {
146 | this.callback = fn
147 | this.performRequest(opts)
148 | }
149 | }
150 |
151 | request (method, resource, data, fn) {
152 | if (typeof data === 'function' && !fn) {
153 | fn = data
154 | data = {}
155 | }
156 |
157 | if (!data) {
158 | data = {}
159 | }
160 |
161 | return promisifyCall(this, this._request, method, resource, data, fn)
162 | }
163 |
164 | prepareFormData (data) {
165 | this.form = new FormData()
166 |
167 | for (const key in data) {
168 | if ({}.hasOwnProperty.call(data, key)) {
169 | const obj = data[key]
170 |
171 | if (isOk(obj)) {
172 | if (key === 'attachment' || key === 'inline') {
173 | if (Array.isArray(obj)) {
174 | for (let i = 0; i < obj.length; i++) {
175 | this.handleAttachmentObject(key, obj[i])
176 | }
177 | } else {
178 | this.handleAttachmentObject(key, obj)
179 | }
180 | } else if (key === 'message') {
181 | this.handleMimeObject(key, obj)
182 | } else if (Array.isArray(obj)) {
183 | obj.forEach((element) => {
184 | if (isOk(element)) {
185 | const value = getDataValue(key, element)
186 |
187 | if (isOk(value)) {
188 | this.form.append(key, value)
189 | }
190 | }
191 | })
192 | } else {
193 | const value = getDataValue(key, obj)
194 |
195 | if (isOk(value)) {
196 | this.form.append(key, value)
197 | }
198 | }
199 | }
200 | }
201 | }
202 |
203 | this.headers = this.form.getHeaders()
204 | }
205 |
206 | handleMimeObject (key, obj) {
207 | if (typeof obj === 'string') {
208 | if (fs.existsSync(obj) && fs.statSync(obj).isFile()) {
209 | this.form.append('message', fs.createReadStream(obj))
210 | } else {
211 | this.form.append('message', Buffer.from(obj), {
212 | 'filename': 'message.mime',
213 | 'contentType': 'message/rfc822',
214 | 'knownLength': obj.length
215 | })
216 | }
217 | } else if (obj instanceof Readable) {
218 | this.form.append('message', obj)
219 | }
220 | }
221 |
222 | handleAttachmentObject (key, obj) {
223 | if (!this.form) this.form = new FormData()
224 |
225 | if (Buffer.isBuffer(obj)) {
226 | debug('appending buffer to form data. key: %s', key)
227 | this.form.append(key, obj, {
228 | 'filename': 'file'
229 | })
230 | } else if (typeof obj === 'string') {
231 | debug('appending stream to form data. key: %s obj: %s', key, obj)
232 | this.form.append(key, fs.createReadStream(obj))
233 | } else if ((typeof obj === 'object') && (obj.readable === true)) {
234 | debug('appending readable stream to form data. key: %s obj: %s', key, obj)
235 | this.form.append(key, obj)
236 | } else if ((typeof obj === 'object') && (obj instanceof Attachment)) {
237 | const attachmentType = obj.getType()
238 |
239 | if (attachmentType === 'path') {
240 | debug('appending attachment stream to form data. key: %s data: %s filename: %s', key, obj.data, obj.filename)
241 | this.form.append(key, fs.createReadStream(obj.data), {
242 | 'filename': obj.filename || 'attached file'
243 | })
244 | } else if (attachmentType === 'buffer') {
245 | debug('appending attachment buffer to form data. key: %s filename: %s', key, obj.filename)
246 | const formOpts = {
247 | 'filename': obj.filename || 'attached file'
248 | }
249 |
250 | if (obj.contentType) {
251 | formOpts.contentType = obj.contentType
252 | }
253 |
254 | if (obj.knownLength) {
255 | formOpts.knownLength = obj.knownLength
256 | }
257 |
258 | this.form.append(key, obj.data, formOpts)
259 | } else if (attachmentType === 'stream') {
260 | if ((obj.knownLength && !obj.contentType) || (!obj.knownLength && obj.contentType)) {
261 | debug('missing content type or length for attachment stream. key: %s', key)
262 | } else {
263 | debug('appending attachment stream to form data. key: %s', key)
264 |
265 | // add all known options
266 | let formOpts = {}
267 | if (obj.filename) {
268 | formOpts.filename = obj.filename
269 | }
270 | if (obj.contentType) {
271 | formOpts.contentType = obj.contentType
272 | }
273 | if (obj.knownLength) {
274 | formOpts.knownLength = obj.knownLength
275 | }
276 |
277 | this.form.append(key, obj.data, formOpts)
278 | }
279 | }
280 | } else {
281 | debug('unknown attachment type. key: %s', key)
282 | }
283 | }
284 |
285 | handleResponse (res) {
286 | let chunks = ''
287 | let error
288 |
289 | res.on('data', (chunk) => {
290 | chunks += chunk
291 | })
292 |
293 | res.on('error', (err) => {
294 | error = err
295 | })
296 |
297 | res.on('end', () => {
298 | let body
299 |
300 | debug('response status code: %s content type: %s error: %s', res.statusCode, res.headers['content-type'], error)
301 |
302 | // FIXME: An ugly hack to overcome invalid response type in mailgun api (see http://bit.ly/1eF30fU).
303 | // We skip content-type validation for 'campaings' endpoint assuming it is JSON.
304 | const skipContentTypeCheck = res.req && res.req.path && res.req.path.match(/\/campaigns/)
305 | const isJSON = res.headers['content-type'] && res.headers['content-type'].indexOf('application/json') >= 0
306 |
307 | if (chunks && !error && (skipContentTypeCheck || isJSON)) {
308 | try {
309 | body = JSON.parse(chunks)
310 | } catch (e) {
311 | error = e
312 | }
313 | }
314 |
315 | if (process.env.DEBUG_MAILGUN_FORCE_RETRY) {
316 | error = new Error('Force retry error')
317 | delete process.env.DEBUG_MAILGUN_FORCE_RETRY
318 | }
319 |
320 | if (!error && res.statusCode !== 200) {
321 | let msg = body || chunks || res.statusMessage
322 |
323 | if (body) {
324 | msg = body.message || body.response
325 | }
326 |
327 | error = new Error(msg)
328 | error.statusCode = res.statusCode
329 | }
330 |
331 | return this.callback(error, body)
332 | })
333 | }
334 |
335 | performRequest (options) {
336 | const method = options.method
337 |
338 | if (this.form && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
339 | let alreadyHandled = false
340 | this.form.submit(options, (err, res) => {
341 | if (alreadyHandled) {
342 | return
343 | }
344 | alreadyHandled = true
345 |
346 | if (err) {
347 | return this.callback(err)
348 | }
349 |
350 | return this.handleResponse(res)
351 | })
352 | } else {
353 | let req
354 |
355 | if (options.protocol === 'http:') {
356 | req = http.request(options, (res) => {
357 | return this.handleResponse(res)
358 | })
359 | } else {
360 | req = https.request(options, (res) => {
361 | return this.handleResponse(res)
362 | })
363 | }
364 |
365 | if (options.timeout) {
366 | req.setTimeout(options.timeout, () => {
367 | // timeout occurs
368 | req.abort()
369 | })
370 | }
371 |
372 | req.on('error', (e) => {
373 | return this.callback(e)
374 | })
375 |
376 | if (this.payload && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
377 | req.write(this.payload)
378 | }
379 |
380 | req.end()
381 | }
382 | }
383 |
384 | defaultTestModeLogger (httpOptions, payload, formData) {
385 | const testlog = debug.enabled ? debug : console.log
386 | testlog('options: %o', httpOptions)
387 | testlog('payload: %o', payload)
388 | testlog('form: %o', formData)
389 | }
390 | }
391 |
392 | module.exports = Request
393 |
--------------------------------------------------------------------------------
/test/messages.test.js:
--------------------------------------------------------------------------------
1 | const auth = require('./data/auth.json')
2 | const fixture = require('./data/fixture.json')
3 | const clone = require('clone')
4 | const assert = require('assert')
5 | const fs = require('fs')
6 | const path = require('path')
7 | const request = require('request')
8 |
9 | const mailgun = require('../')({
10 | 'apiKey': auth.api_key,
11 | 'domain': auth.domain
12 | })
13 |
14 | describe('Messages', () => {
15 | beforeEach((done) => {
16 | setTimeout(done, 500)
17 | })
18 |
19 | it('test messages.send() invalid "from"', (done) => {
20 | mailgun.messages().send({
21 | 'to': fixture.message.to
22 | }, (err) => {
23 | assert.ok(err)
24 | assert(/Missing parameter 'from'/.test(err.message))
25 | done()
26 | })
27 | })
28 |
29 | it('test messages().send()', (done) => {
30 | const msg = clone(fixture.message)
31 |
32 | mailgun.messages().send(msg, (err, body) => {
33 | assert.ifError(err)
34 | assert.ok(body.id)
35 | assert.ok(body.message)
36 | assert(/Queued. Thank you./.test(body.message))
37 | done()
38 | })
39 | })
40 |
41 | it('test messages().send() with recipient vars', (done) => {
42 | const msg = clone(fixture.message_recipient_vars)
43 |
44 | mailgun.messages().send(msg, (err, body) => {
45 | assert.ifError(err)
46 | assert.ok(body.id)
47 | assert.ok(body.message)
48 | assert(/Queued. Thank you./.test(body.message))
49 | done()
50 | })
51 | })
52 |
53 | it('test messages().send() with invalid attachment should go ok', (done) => {
54 | const msg = clone(fixture.message)
55 |
56 | msg.attachment = 123
57 |
58 | mailgun.messages().send(msg, (err, body) => {
59 | assert.ifError(err)
60 | assert.ok(body.id)
61 | assert.ok(body.message)
62 | assert(/Queued. Thank you./.test(body.message))
63 | done()
64 | })
65 | })
66 |
67 | it('test messages().send() with attachment using filename', (done) => {
68 | const msg = clone(fixture.message)
69 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
70 |
71 | msg.attachment = filename
72 |
73 | mailgun.messages().send(msg, (err, body) => {
74 | assert.ifError(err)
75 | assert.ok(body.id)
76 | assert.ok(body.message)
77 | assert(/Queued. Thank you./.test(body.message))
78 | done()
79 | })
80 | })
81 |
82 | it('test messages().send() with attachment using filename with important flag', (done) => {
83 | const msg = clone(fixture.message)
84 |
85 | msg.important = true
86 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
87 |
88 | msg.attachment = filename
89 |
90 | mailgun.messages().send(msg, (err, body) => {
91 | assert.ifError(err)
92 | assert.ok(body.id)
93 | assert.ok(body.message)
94 | assert(/Queued. Thank you./.test(body.message))
95 | done()
96 | })
97 | })
98 |
99 | it('test messages().send() with attachment using filename and undefined v: var', (done) => {
100 | const msg = clone(fixture.message)
101 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
102 |
103 | msg.attachment = filename
104 |
105 | msg['v:someFoo'] = undefined
106 |
107 | mailgun.messages().send(msg, (err, body) => {
108 | assert.ifError(err)
109 | assert.ok(body.id)
110 | assert.ok(body.message)
111 | assert(/Queued. Thank you./.test(body.message))
112 | done()
113 | })
114 | })
115 |
116 | it('test messages().send() with attachment using file buffer', (done) => {
117 | const msg = clone(fixture.message)
118 |
119 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
120 | const file = fs.readFileSync(filename)
121 |
122 | msg.attachment = file
123 |
124 | mailgun.messages().send(msg, (err, body) => {
125 | assert.ifError(err)
126 | assert.ok(body.id)
127 | assert.ok(body.message)
128 | assert(/Queued. Thank you./.test(body.message))
129 | done()
130 | })
131 | })
132 |
133 | it('test messages().send() with attachment using Attachment object (Buffer)', (done) => {
134 | const msg = clone(fixture.message)
135 |
136 | const filename = '/data/mailgun_logo.png'
137 | const filepath = path.join(__dirname, filename)
138 | const file = fs.readFileSync(filepath)
139 |
140 | msg.attachment = new mailgun.Attachment({
141 | 'data': file,
142 | filename
143 | })
144 |
145 | mailgun.messages().send(msg, (err, body) => {
146 | assert.ifError(err)
147 | assert.ok(body.id)
148 | assert.ok(body.message)
149 | assert(/Queued. Thank you./.test(body.message))
150 | done()
151 | })
152 | })
153 |
154 | it('test messages().send() with attachment using Attachment object (file)', (done) => {
155 | const msg = clone(fixture.message)
156 |
157 | const filename = '/data/mailgun_logo.png'
158 | const filepath = path.join(__dirname, filename)
159 |
160 | msg.attachment = new mailgun.Attachment({
161 | 'data': filepath,
162 | 'filename': 'my_custom_name.png'
163 | })
164 |
165 | mailgun.messages().send(msg, (err, body) => {
166 | assert.ifError(err)
167 | assert.ok(body.id)
168 | assert.ok(body.message)
169 | assert(/Queued. Thank you./.test(body.message))
170 | done()
171 | })
172 | })
173 |
174 | it('test messages().send() with multiple attachments', (done) => {
175 | const msg = clone(fixture.message)
176 | const filename = path.join(__dirname, '/data/fixture.json')
177 | const filename2 = path.join(__dirname, '/data/mailgun_logo.png')
178 | const file = fs.readFileSync(filename2)
179 |
180 | msg.attachment = [filename, file]
181 |
182 | mailgun.messages().send(msg, (err, body) => {
183 | assert.ifError(err)
184 | assert.ok(body.id)
185 | assert.ok(body.message)
186 | assert(/Queued. Thank you./.test(body.message))
187 | done()
188 | })
189 | })
190 |
191 | it('test messages().send() with invalid inline attachment should go ok', (done) => {
192 | const msg = clone(fixture.message)
193 |
194 | msg.inline = 123
195 |
196 | mailgun.messages().send(msg, (err, body) => {
197 | assert.ifError(err)
198 | assert.ok(body.id)
199 | assert.ok(body.message)
200 | assert(/Queued. Thank you./.test(body.message))
201 | done()
202 | })
203 | })
204 |
205 | it('test messages().send() with inline attachment using filename', (done) => {
206 | const msg = clone(fixture.message)
207 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
208 |
209 | msg.inline = filename
210 |
211 | mailgun.messages().send(msg, (err, body) => {
212 | assert.ifError(err)
213 | assert.ok(body.id)
214 | assert.ok(body.message)
215 | assert(/Queued. Thank you./.test(body.message))
216 | done()
217 | })
218 | })
219 |
220 | it('test messages().send() with inline attachment using file buffer', (done) => {
221 | const msg = clone(fixture.message)
222 |
223 | const filename = path.join(__dirname, '/data/mailgun_logo.png')
224 | const file = fs.readFileSync(filename)
225 |
226 | msg.inline = file
227 |
228 | mailgun.messages().send(msg, (err, body) => {
229 | assert.ifError(err)
230 | assert.ok(body.id)
231 | assert.ok(body.message)
232 | assert(/Queued. Thank you./.test(body.message))
233 | done()
234 | })
235 | })
236 |
237 | it('test messages().send() with inline attachment using Attachment object (Buffer)', (done) => {
238 | const msg = clone(fixture.message)
239 |
240 | const filename = '/data/mailgun_logo.png'
241 | const filepath = path.join(__dirname, filename)
242 | const file = fs.readFileSync(filepath)
243 |
244 | msg.inline = new mailgun.Attachment({
245 | 'data': file,
246 | filename
247 | })
248 |
249 | mailgun.messages().send(msg, (err, body) => {
250 | assert.ifError(err)
251 | assert.ok(body.id)
252 | assert.ok(body.message)
253 | assert(/Queued. Thank you./.test(body.message))
254 | done()
255 | })
256 | })
257 |
258 | it('test messages().send() with inline attachment using Attachment object (file)', (done) => {
259 | const msg = clone(fixture.message)
260 |
261 | const filename = '/data/mailgun_logo.png'
262 | const filepath = path.join(__dirname, filename)
263 |
264 | msg.inline = new mailgun.Attachment({
265 | 'data': filepath,
266 | 'filename': 'my_custom_name.png'
267 | })
268 |
269 | mailgun.messages().send(msg, (err, body) => {
270 | assert.ifError(err)
271 | assert.ok(body.id)
272 | assert.ok(body.message)
273 | assert(/Queued. Thank you./.test(body.message))
274 | done()
275 | })
276 | })
277 |
278 | it('test messages().send() with multiple inline attachments', (done) => {
279 | const msg = clone(fixture.message)
280 | const filename = path.join(__dirname, '/data/fixture.json')
281 | const filename2 = path.join(__dirname, '/data/mailgun_logo.png')
282 | const file = fs.readFileSync(filename2)
283 |
284 | msg.inline = [filename, file]
285 |
286 | mailgun.messages().send(msg, (err, body) => {
287 | assert.ifError(err)
288 | assert.ok(body.id)
289 | assert.ok(body.message)
290 | assert(/Queued. Thank you./.test(body.message))
291 | done()
292 | })
293 | })
294 |
295 | it('test messages().send() with multiple inline and normal attachments', (done) => {
296 | const msg = clone(fixture.message)
297 | const filename = path.join(__dirname, '/data/fixture.json')
298 | const filename2 = path.join(__dirname, '/data/mailgun_logo.png')
299 |
300 | msg.attachment = filename
301 | msg.inline = filename2
302 |
303 | mailgun.messages().send(msg, (err, body) => {
304 | assert.ifError(err)
305 | assert.ok(body.id)
306 | assert.ok(body.message)
307 | assert(/Queued. Thank you./.test(body.message))
308 | done()
309 | })
310 | })
311 |
312 | it('test messages().send() with multiple tags', (done) => {
313 | const msg = clone(fixture.message)
314 |
315 | msg['o:tag'] = ['tag1', 'tag2', 'tag3']
316 |
317 | mailgun.messages().send(msg, (err, body) => {
318 | assert.ifError(err)
319 | assert.ok(body.id)
320 | assert.ok(body.message)
321 | assert(/Queued. Thank you./.test(body.message))
322 | done()
323 | })
324 | })
325 |
326 | it('test messages().send() with multiple tags and normal attachment', (done) => {
327 | const msg = clone(fixture.message)
328 | const filename = path.join(__dirname, '/data/fixture.json')
329 |
330 | msg.attachment = filename
331 |
332 | msg['o:tag'] = ['tag1', 'tag2', 'tag3']
333 |
334 | mailgun.messages().send(msg, (err, body) => {
335 | assert.ifError(err)
336 | assert.ok(body.id)
337 | assert.ok(body.message)
338 | assert(/Queued. Thank you./.test(body.message))
339 | done()
340 | })
341 | })
342 |
343 | it('test messages().send() with attachment using stream', (done) => {
344 | const msg = clone(fixture.message)
345 |
346 | const file = request('https://www.google.ca/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png')
347 |
348 | msg.attachment = file
349 |
350 | mailgun.messages().send(msg, (err, body) => {
351 | assert.ifError(err)
352 | assert.ok(body.id)
353 | assert.ok(body.message)
354 | assert(/Queued. Thank you./.test(body.message))
355 | done()
356 | })
357 | })
358 |
359 | it('test messages().send() with attachment using Attachment object (stream)', (done) => {
360 | const msg = clone(fixture.message)
361 |
362 | const filename = '/data/mailgun_logo.png'
363 | const filepath = path.join(__dirname, filename)
364 | const fileStream = fs.createReadStream(filepath)
365 | const fileStat = fs.statSync(filepath)
366 |
367 | msg.attachment = new mailgun.Attachment({
368 | 'data': fileStream,
369 | 'filename': 'my_custom_name.png',
370 | 'knownLength': fileStat.size,
371 | 'contentType': 'image/png'
372 | })
373 |
374 | mailgun.messages().send(msg, (err, body) => {
375 | assert.ifError(err)
376 | assert.ok(body.id)
377 | assert.ok(body.message)
378 | assert(/Queued. Thank you./.test(body.message))
379 | done()
380 | })
381 | })
382 |
383 | it('test messages().send() with attachment using Attachment object (stream) and only filename', (done) => {
384 | const msg = clone(fixture.message)
385 |
386 | const file = request('https://www.google.ca/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png')
387 |
388 | msg.attachment = new mailgun.Attachment({
389 | 'data': file,
390 | 'filename': 'my_custom_name.png'
391 | })
392 |
393 | mailgun.messages().send(msg, (err, body) => {
394 | assert.ifError(err)
395 | assert.ok(body.id)
396 | assert.ok(body.message)
397 | assert(/Queued. Thank you./.test(body.message))
398 | done()
399 | })
400 | })
401 |
402 | it('test messages().send() with custom data', (done) => {
403 | const msg = clone(fixture.message)
404 |
405 | msg['v:whatever'] = 123
406 | msg['v:test'] = true
407 |
408 | mailgun.messages().send(msg, (err, body) => {
409 | assert.ifError(err)
410 | assert.ok(body.id)
411 | assert.ok(body.message)
412 | assert(/Queued. Thank you./.test(body.message))
413 | done()
414 | })
415 | })
416 |
417 | it('test messages().send() with custom data as object', (done) => {
418 | const msg = clone(fixture.message)
419 |
420 | msg['v:my-custom-data'] = {
421 | 'whatever': 123,
422 | 'test': true
423 | }
424 |
425 | mailgun.messages().send(msg, (err, body) => {
426 | assert.ifError(err)
427 | assert.ok(body.id)
428 | assert.ok(body.message)
429 | assert(/Queued. Thank you./.test(body.message))
430 | done()
431 | })
432 | })
433 |
434 | describe('Retry', () => {
435 | it('messages().send() should work with retry option', (done) => {
436 | const mg = new mailgun.Mailgun({
437 | 'apiKey': auth.api_key,
438 | 'domain': auth.domain,
439 | 'retry': 3
440 | })
441 |
442 | const msg = clone(fixture.message)
443 |
444 | mg.messages().send(msg, (err, body) => {
445 | assert.ifError(err)
446 | assert.ok(body.id)
447 | assert.ok(body.message)
448 | assert(/Queued. Thank you./.test(body.message))
449 | done()
450 | })
451 | })
452 |
453 | it('messages().send() should retry when request fails', (done) => {
454 | process.env.DEBUG_MAILGUN_FORCE_RETRY = true
455 | const mg = new mailgun.Mailgun({
456 | 'apiKey': auth.api_key,
457 | 'domain': auth.domain,
458 | 'retry': 3
459 | })
460 |
461 | const msg = clone(fixture.message)
462 |
463 | mg.messages().send(msg, (err, body) => {
464 | assert.ifError(err)
465 | assert.ok(body.id)
466 | assert.ok(body.message)
467 | assert(/Queued. Thank you./.test(body.message))
468 | done()
469 | })
470 | })
471 |
472 | it('messages().send() should work with retry option as an object', (done) => {
473 | const mg = new mailgun.Mailgun({
474 | 'apiKey': auth.api_key,
475 | 'domain': auth.domain,
476 | 'retry': {
477 | 'times': 3,
478 | 'interval': 100
479 | }
480 | })
481 |
482 | const msg = clone(fixture.message)
483 |
484 | mg.messages().send(msg, (err, body) => {
485 | assert.ifError(err)
486 | assert.ok(body.id)
487 | assert.ok(body.message)
488 | assert(/Queued. Thank you./.test(body.message))
489 | done()
490 | })
491 | })
492 |
493 | it('messages().send() should retry when request fails with a delay of 100 ms between attempts', (done) => {
494 | process.env.DEBUG_MAILGUN_FORCE_RETRY = true
495 | const mg = new mailgun.Mailgun({
496 | 'apiKey': auth.api_key,
497 | 'domain': auth.domain,
498 | 'retry': {
499 | 'times': 3,
500 | 'interval': 100
501 | }
502 | })
503 |
504 | const msg = clone(fixture.message)
505 |
506 | mg.messages().send(msg, (err, body) => {
507 | assert.ifError(err)
508 | assert.ok(body.id)
509 | assert.ok(body.message)
510 | assert(/Queued. Thank you./.test(body.message))
511 | done()
512 | })
513 | })
514 | })
515 | })
516 |
--------------------------------------------------------------------------------
/lib/schema.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'definitions': {
3 | 'message': {
4 | 'description': 'This API allows you to send, access, and delete mesages programmatically.',
5 | 'links': [{
6 | 'description': 'Returns a single message in JSON format. To get full MIME message set MIME to true',
7 | 'href': '/messages/{message}',
8 | 'method': 'GET',
9 | 'title': 'info',
10 | 'properties': {
11 | 'MIME': {
12 | 'type': 'boolean'
13 | }
14 | }
15 | },
16 | {
17 | 'description': 'Sends a message by assembling it from the components.',
18 | 'href': '/messages',
19 | 'method': 'POST',
20 | 'title': 'send',
21 | 'properties': {
22 | 'from': {
23 | 'type': 'string'
24 | }
25 | },
26 | 'required': ['from']
27 | },
28 | {
29 | 'description': 'Sends a message in MIME format.',
30 | 'href': '/messages.mime',
31 | 'method': 'POST',
32 | 'title': 'send-mime',
33 | 'properties': {
34 | 'message': {
35 | 'type': ['string', 'object']
36 | }
37 | }
38 | },
39 | {
40 | 'description': 'To delete an inbound message that has been stored via the store() action.',
41 | 'href': '/messages/{message}',
42 | 'method': 'DELETE',
43 | 'title': 'delete'
44 | }
45 | ]
46 | },
47 | 'domain': {
48 | 'description': 'This API allows you to create, access, and validate domains programmatically.',
49 | 'links': [{
50 | 'description': 'Returns a list of domains under your account in JSON.',
51 | 'href': '/domains',
52 | 'method': 'GET',
53 | 'title': 'list',
54 | 'properties': {
55 | 'limit': {
56 | 'type': 'number'
57 | },
58 | 'skip': {
59 | 'type': 'number'
60 | }
61 | }
62 | },
63 | {
64 | 'description': 'Returns a single domain, including credentials and DNS records.',
65 | 'href': '/domains/{domain}',
66 | 'method': 'GET',
67 | 'title': 'info'
68 | },
69 | {
70 | 'description': 'Create a new domain.',
71 | 'href': '/domains',
72 | 'method': 'POST',
73 | 'title': 'create',
74 | 'properties': {
75 | 'name': {
76 | 'type': 'string'
77 | },
78 | 'wildcard': {
79 | 'type': 'boolean'
80 | }
81 | },
82 | 'required': ['name']
83 | },
84 | {
85 | 'description': 'Delete a domain from your account.',
86 | 'href': '/domains/{domain}',
87 | 'method': 'DELETE',
88 | 'title': 'delete'
89 | },
90 | {
91 | 'description': 'Verifies and returns a single domain, including credentials and DNS records.',
92 | 'href': '/domains/{domain}/verify',
93 | 'method': 'PUT',
94 | 'title': 'verify'
95 | }
96 | ]
97 | },
98 | 'credentials': {
99 | 'description': 'Programmatically get and modify domain credentials.',
100 | 'links': [{
101 | 'description': 'Returns a list of SMTP credentials for the defined domain.',
102 | 'href': '/domains/{domain}/credentials',
103 | 'method': 'GET',
104 | 'title': 'list',
105 | 'properties': {
106 | 'limit': {
107 | 'type': 'number'
108 | },
109 | 'skip': {
110 | 'type': 'number'
111 | }
112 | }
113 | },
114 | {
115 | 'description': 'Creates a new set of SMTP credentials for the defined domain.',
116 | 'href': '/domains/{domain}/credentials',
117 | 'method': 'POST',
118 | 'title': 'create',
119 | 'properties': {
120 | 'login': {
121 | 'type': 'string'
122 | },
123 | 'password': {
124 | 'type': 'string'
125 | }
126 | },
127 | 'required': ['login', 'password']
128 | },
129 | {
130 | 'description': 'Updates the specified SMTP credentials. Currently only the password can be changed.',
131 | 'href': '/domains/{domain}/credentials/{login}',
132 | 'method': 'PUT',
133 | 'title': 'update',
134 | 'properties': {
135 | 'password': {
136 | 'type': 'string'
137 | }
138 | },
139 | 'required': ['password']
140 | },
141 | {
142 | 'description': 'Deletes the defined SMTP credentials.',
143 | 'href': '/domains/{domain}/credentials/{login}',
144 | 'method': 'DELETE',
145 | 'title': 'delete'
146 | }
147 | ]
148 | },
149 | 'complaints': {
150 | 'description': 'This API allows you to programmatically download the list of users who have complained, add a complaint, or delete a complaint.',
151 | 'links': [{
152 | 'description': 'Fetches the list of complaints.',
153 | 'href': '/complaints',
154 | 'method': 'GET',
155 | 'title': 'list',
156 | 'properties': {
157 | 'limit': {
158 | 'type': 'number'
159 | },
160 | 'skip': {
161 | 'type': 'number'
162 | }
163 | }
164 | },
165 | {
166 | 'description': 'Adds an address to the complaints table.',
167 | 'href': '/complaints',
168 | 'method': 'POST',
169 | 'title': 'create',
170 | 'properties': {
171 | 'address': {
172 | 'type': 'string'
173 | }
174 | },
175 | 'required': ['address']
176 | },
177 | {
178 | 'description': 'Fetches a single spam complaint by a given email address.',
179 | 'href': '/complaints/{address}',
180 | 'method': 'GET',
181 | 'title': 'info'
182 | },
183 | {
184 | 'description': 'Removes a given spam complaint.',
185 | 'href': '/complaints/{address}',
186 | 'method': 'DELETE',
187 | 'title': 'delete'
188 | }
189 | ]
190 | },
191 | 'unsubscribes': {
192 | 'description': 'This API allows you to programmatically download the list of recipients who have unsubscribed from your emails. You can also programmatically “clear” the unsubscribe event.',
193 | 'links': [{
194 | 'description': 'Fetches the list of unsubscribes.',
195 | 'href': '/unsubscribes',
196 | 'method': 'GET',
197 | 'title': 'list',
198 | 'properties': {
199 | 'limit': {
200 | 'type': 'number'
201 | },
202 | 'skip': {
203 | 'type': 'number'
204 | }
205 | }
206 | },
207 | {
208 | 'description': 'Retreives a single unsubscribe record.',
209 | 'href': '/unsubscribes/{address}',
210 | 'method': 'GET',
211 | 'title': 'info'
212 | },
213 | {
214 | 'description': 'Removes an address from the unsubscribes table.',
215 | 'href': '/unsubscribes/{address}',
216 | 'method': 'DELETE',
217 | 'title': 'delete'
218 | },
219 | {
220 | 'description': 'Adds address to unsubscribed table.',
221 | 'href': '/unsubscribes',
222 | 'method': 'POST',
223 | 'title': 'create'
224 | }
225 | ]
226 | },
227 | 'bounces': {
228 | 'description': 'Mailgun automatically handles bounced emails. The list of bounced addresses can be accessed programmatically.',
229 | 'links': [{
230 | 'description': 'Fetches the list of bounces.',
231 | 'href': '/bounces',
232 | 'method': 'GET',
233 | 'title': 'list',
234 | 'properties': {
235 | 'limit': {
236 | 'type': 'number'
237 | },
238 | 'skip': {
239 | 'type': 'number'
240 | }
241 | }
242 | },
243 | {
244 | 'description': 'Fetches a single bounce event by a given email address.',
245 | 'href': '/bounces/{address}',
246 | 'method': 'GET',
247 | 'title': 'info'
248 | },
249 | {
250 | 'description': 'Clears a given bounce event.',
251 | 'href': '/bounces/{address}',
252 | 'method': 'DELETE',
253 | 'title': 'delete'
254 | },
255 | {
256 | 'description': 'Adds a permanent bounce to the bounces table. Updates the existing record if already here.',
257 | 'href': '/bounces',
258 | 'method': 'POST',
259 | 'title': 'create',
260 | 'properties': {
261 | 'address': {
262 | 'type': 'string'
263 | },
264 | 'code': {
265 | 'type': 'number'
266 | },
267 | 'error': {
268 | 'type': 'string'
269 | }
270 | },
271 | 'required': ['address']
272 | }
273 | ]
274 | },
275 | 'routes': {
276 | 'description': 'Mailgun Routes are a powerful way to handle the incoming traffic. This API allows you to work with routes programmatically.',
277 | 'links': [{
278 | 'description': 'Fetches the list of routes.',
279 | 'href': '/routes',
280 | 'method': 'GET',
281 | 'title': 'list',
282 | 'properties': {
283 | 'limit': {
284 | 'type': 'number'
285 | },
286 | 'skip': {
287 | 'type': 'number'
288 | }
289 | }
290 | },
291 | {
292 | 'description': 'Returns a single route object based on its ID.',
293 | 'href': '/routes/{id}',
294 | 'method': 'GET',
295 | 'title': 'info'
296 | },
297 | {
298 | 'description': 'Creates a new route.',
299 | 'href': '/routes',
300 | 'method': 'POST',
301 | 'title': 'create',
302 | 'properties': {
303 | 'limit': {
304 | 'priority': 'number'
305 | },
306 | 'description': {
307 | 'type': 'string'
308 | },
309 | 'expression': {
310 | 'type': 'string'
311 | }
312 | },
313 | 'required': ['expression']
314 | },
315 | {
316 | 'description': 'Updates a given route by ID.',
317 | 'href': '/routes/{id}',
318 | 'method': 'PUT',
319 | 'title': 'update',
320 | 'properties': {
321 | 'limit': {
322 | 'priority': 'number'
323 | },
324 | 'description': {
325 | 'type': 'string'
326 | },
327 | 'expression': {
328 | 'type': 'string'
329 | }
330 | }
331 | },
332 | {
333 | 'description': 'Deletes a route based on the id.',
334 | 'href': '/routes/{id}',
335 | 'method': 'DELETE',
336 | 'title': 'delete'
337 | }
338 | ]
339 | },
340 | 'list': {
341 | 'description': 'You can programmatically work with mailing lists and mailing list members using Mailgun Mailing List API.',
342 | 'links': [{
343 | 'description': 'Returns a list of mailing lists under your account.',
344 | 'href': '/lists',
345 | 'method': 'GET',
346 | 'title': 'list',
347 | 'properties': {
348 | 'address': {
349 | 'type': 'string'
350 | },
351 | 'limit': {
352 | 'type': 'number'
353 | },
354 | 'skip': {
355 | 'type': 'number'
356 | }
357 | }
358 | },
359 | {
360 | 'description': 'Returns a single mailing list by a given address.',
361 | 'href': '/lists/{address}',
362 | 'method': 'GET',
363 | 'title': 'info'
364 | },
365 | {
366 | 'description': 'Creates a new mailing list.',
367 | 'href': '/lists',
368 | 'method': 'POST',
369 | 'title': 'create',
370 | 'properties': {
371 | 'address': {
372 | 'type': 'string'
373 | },
374 | 'name': {
375 | 'type': 'string'
376 | },
377 | 'description': {
378 | 'type': 'string'
379 | },
380 | 'access_level': {
381 | 'type': 'string'
382 | }
383 | },
384 | 'required': ['address']
385 | },
386 | {
387 | 'description': 'Update mailing list properties, such as address, description or name.',
388 | 'href': '/lists/{address}',
389 | 'method': 'PUT',
390 | 'title': 'update',
391 | 'properties': {
392 | 'address': {
393 | 'type': 'string'
394 | },
395 | 'name': {
396 | 'type': 'string'
397 | },
398 | 'description': {
399 | 'type': 'string'
400 | },
401 | 'access_level': {
402 | 'type': 'string'
403 | }
404 | }
405 | },
406 | {
407 | 'description': 'Deletes a mailing list.',
408 | 'href': '/lists/{address}',
409 | 'method': 'DELETE',
410 | 'title': 'delete'
411 | }
412 | ]
413 | },
414 | 'members': {
415 | 'description': 'Programatically work with mailing lists members.',
416 | 'links': [{
417 | 'description': 'Fetches the list of mailing list members.',
418 | 'href': '/lists/{address}/members',
419 | 'method': 'GET',
420 | 'title': 'list',
421 | 'properties': {
422 | 'subscribed': {
423 | 'type': 'boolean'
424 | },
425 | 'limit': {
426 | 'type': 'number'
427 | },
428 | 'skip': {
429 | 'type': 'number'
430 | }
431 | }
432 | },
433 | {
434 | 'description': 'Paginate over list members in the given mailing list',
435 | 'href': '/lists/{address}/members/pages',
436 | 'method': 'GET',
437 | 'title': 'page',
438 | 'properties': {
439 | 'subscribed': {
440 | 'type': 'boolean'
441 | },
442 | 'limit': {
443 | 'type': 'number'
444 | },
445 | 'page': {
446 | 'type': 'string'
447 | },
448 | 'address': {
449 | 'type': 'string'
450 | }
451 | }
452 | },
453 | {
454 | 'description': 'Retrieves a mailing list member.',
455 | 'href': '/lists/{address}/members/{member_address}',
456 | 'method': 'GET',
457 | 'title': 'info'
458 | },
459 | {
460 | 'description': 'Adds a member to the mailing list.',
461 | 'href': '/lists/{address}/members',
462 | 'method': 'POST',
463 | 'title': 'create',
464 | 'properties': {
465 | 'address': {
466 | 'type': 'string'
467 | },
468 | 'name': {
469 | 'type': 'string'
470 | },
471 | 'vars': {
472 | 'type': 'object'
473 | },
474 | 'subscribed': {
475 | 'type': 'boolean'
476 | },
477 | 'upsert': {
478 | 'type': 'string'
479 | }
480 | },
481 | 'required': ['address']
482 | },
483 | {
484 | 'description': 'Adds multiple members, up to 1,000 per call, to a Mailing List.',
485 | 'href': '/lists/{address}/members.json',
486 | 'method': 'POST',
487 | 'title': 'add',
488 | 'properties': {
489 | 'members': {
490 | 'type': 'array'
491 | },
492 | 'upsert': {
493 | 'type': 'boolean'
494 | }
495 | },
496 | 'required': ['members']
497 | },
498 | {
499 | 'description': 'Updates a mailing list member with given properties.',
500 | 'href': '/lists/{address}/members/{member_address}',
501 | 'method': 'PUT',
502 | 'title': 'update',
503 | 'properties': {
504 | 'address': {
505 | 'type': 'string'
506 | },
507 | 'name': {
508 | 'type': 'string'
509 | },
510 | 'vars': {
511 | 'type': 'object'
512 | },
513 | 'subscribed': {
514 | 'type': 'boolean'
515 | }
516 | }
517 | },
518 | {
519 | 'description': 'Delete a mailing list member.',
520 | 'href': '/lists/{address}/members/{member_address}',
521 | 'method': 'DELETE',
522 | 'title': 'delete'
523 | }
524 | ]
525 | },
526 | 'stats': {
527 | 'description': 'Various data and event statistics for you mailgun account. See http://documentation.mailgun.com/api-stats.html',
528 | 'links': [{
529 | 'description': 'Returns a list of event stat items. Each record represents counts for one event per one day.',
530 | 'href': '/stats',
531 | 'method': 'GET',
532 | 'title': 'list',
533 | 'properties': {
534 | 'limit': {
535 | 'type': 'number'
536 | },
537 | 'skip': {
538 | 'type': 'number'
539 | },
540 | 'start-date': {
541 | 'type': 'string'
542 | }
543 | }
544 | }]
545 | },
546 | 'tags': {
547 | 'description': 'Deletes all counters for particular tag and the tag itself. See http://documentation.mailgun.com/api-stats.html',
548 | 'links': [{
549 | 'description': 'List all tags.',
550 | 'href': '/tags',
551 | 'method': 'GET',
552 | 'title': 'list'
553 | },
554 | {
555 | 'description': 'Gets a specific tag.',
556 | 'href': '/tags/{tag}',
557 | 'method': 'GET',
558 | 'title': 'info'
559 | },
560 | {
561 | 'description': 'Returns statistics for a given tag.',
562 | 'href': '/tags/{tag}/stats',
563 | 'method': 'GET',
564 | 'title': 'info',
565 | 'properties': {
566 | 'event': {
567 | 'type': 'array'
568 | },
569 | 'start': {
570 | 'type': 'string'
571 | },
572 | 'end': {
573 | 'type': 'string'
574 | },
575 | 'resolution': {
576 | 'type': 'string'
577 | },
578 | 'duration': {
579 | 'type': 'string'
580 | }
581 | },
582 | 'required': ['event']
583 | },
584 | {
585 | 'description': 'Returns a list of countries of origin for a given domain for different event types.',
586 | 'href': '/tags/{tag}/stats/aggregates/countries',
587 | 'method': 'GET',
588 | 'title': 'list'
589 | },
590 | {
591 | 'description': 'Returns a list of email providers for a given domain for different event types.',
592 | 'href': '/tags/{tag}/stats/aggregates/providers',
593 | 'method': 'GET',
594 | 'title': 'list'
595 | },
596 | {
597 | 'description': 'Returns a list of devices for a given domain that have triggered event types.',
598 | 'href': '/tags/{tag}/stats/aggregates/devices',
599 | 'method': 'GET',
600 | 'title': 'list'
601 | },
602 | {
603 | 'description': 'Deletes all counters for particular tag and the tag itself.',
604 | 'href': '/tags/{tag}',
605 | 'method': 'DELETE',
606 | 'title': 'delete'
607 | }
608 | ]
609 | },
610 | 'events': {
611 | 'description': 'Query events that happen to your emails. See http://documentation.mailgun.com/api-events.html',
612 | 'links': [{
613 | 'description': 'Queries event records.',
614 | 'href': '/events',
615 | 'method': 'GET',
616 | 'title': 'get',
617 | 'properties': {
618 | 'begin': {
619 | 'type': 'string'
620 | },
621 | 'end': {
622 | 'type': 'string'
623 | },
624 | 'ascending': {
625 | 'type': 'string'
626 | },
627 | 'limit': {
628 | 'type': 'number'
629 | },
630 | 'pretty': {
631 | 'type': 'string'
632 | }
633 | }
634 | }]
635 | },
636 | 'tracking': {
637 | 'description': 'Programmatically get and modify domain tracking settings.',
638 | 'links': [{
639 | 'description': 'Returns tracking settings for a domain.',
640 | 'href': '/domains/{domain}/tracking',
641 | 'method': 'GET',
642 | 'title': 'info'
643 | },
644 | {
645 | 'description': 'Updates the open tracking settings for a domain.',
646 | 'href': '/domains/{domain}/tracking/open',
647 | 'method': 'PUT',
648 | 'title': 'update',
649 | 'properties': {
650 | 'active': {
651 | 'type': ['string', 'boolean']
652 | }
653 | },
654 | 'required': ['active']
655 | },
656 | {
657 | 'description': 'Updates the click tracking settings for a domain.',
658 | 'href': '/domains/{domain}/tracking/click',
659 | 'method': 'PUT',
660 | 'title': 'update',
661 | 'properties': {
662 | 'active': {
663 | 'type': ['string', 'boolean']
664 | }
665 | },
666 | 'required': ['active']
667 | },
668 | {
669 | 'description': 'Updates the unsubscribe tracking settings for a domain.',
670 | 'href': '/domains/{domain}/tracking/unsubscribe',
671 | 'method': 'PUT',
672 | 'title': 'update',
673 | 'properties': {
674 | 'active': {
675 | 'type': 'boolean'
676 | },
677 | 'html_footer': {
678 | 'type': 'string'
679 | },
680 | 'text_footer': {
681 | 'type': 'string'
682 | }
683 | },
684 | 'required': ['active']
685 | }
686 | ]
687 | }
688 | }
689 | }
690 |
--------------------------------------------------------------------------------