Comparing version 0.1.6 to 0.4.0
362
mailgun.js
@@ -0,79 +1,315 @@ | ||
// | ||
// Copyright (C) 2011 Patrick Stein | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
// | ||
// TODO - better error handling on requests | ||
// Dirt simple includes. Nice that we can keep things simple :) | ||
var http = require('http'), | ||
querystring = require('querystring'); | ||
//constants | ||
// Mailgun options constants. See Mailgun's API docs for details. | ||
var MAILGUN_TAG = 'X-Mailgun-Tag', | ||
CAMPAIGN_ID = 'X-Campaign-Id'; | ||
// Utility dumb XML parsing helper. Builds a regex of the form | ||
// `<input>\s*(.*?)\s*</input>`, and memoizes for a slight optimization. | ||
var xre = function() { | ||
var cache = {}; | ||
return function(input) { | ||
// Try to fetch the memoized version. | ||
if (cache.hasOwnProperty(input)) return cache[input]; | ||
// Otherwise build it and return it. | ||
var re = new RegExp('<' + input + '>\\s*(.*?)\\s*</' + input + '>', 'im'); | ||
cache[input] = re; | ||
return re; | ||
}; | ||
}(); | ||
// This class is used to tie functionality to an API key, rather than | ||
// using a global initialization function that forces people to use | ||
// only one API key per application. | ||
var Mailgun = function(apiKey) { | ||
//the docs use a base64 header for auth, but that doesn't seem to work... | ||
//var apiKey64 = new Buffer(apiKey).toString('base64'); | ||
this.sendText = function(sender, recipients, subject, text, servername, options, callback) { | ||
//defaults | ||
servername = servername || ''; | ||
options = options || {}; | ||
//be flexible with recipients | ||
if (typeof(recipients) == 'string') | ||
// Authentication uses the api key in base64 form, so we cache that | ||
// here. | ||
this._apiKey64 = new Buffer('api:' + apiKey).toString('base64'); | ||
this._apiKey = apiKey; | ||
}; | ||
Mailgun.prototype = {}; | ||
// Utility method to set up required http options. | ||
Mailgun.prototype._createHttpOptions = function(resource, method, servername) { | ||
return { | ||
host: 'mailgun.net', | ||
port: 80, | ||
method: method, | ||
path: '/api/' + resource + (servername ? '?servername=' + servername : ''), | ||
headers: { | ||
'Authorization': 'Basic ' + this._apiKey64 | ||
} | ||
}; | ||
} | ||
// | ||
// Here be the email sending code. | ||
// | ||
Mailgun.prototype.sendText = function(sender, recipients, subject, text) { | ||
// These are flexible arguments, so we define them here to make | ||
// sure they're in scope. | ||
var servername = ''; | ||
var options = {}; | ||
var callback = null; | ||
// Less than 4 arguments means we're missing something that prevents | ||
// us from even sending an email, so we fail. | ||
if (arguments.length < 4) | ||
throw new Error('Missing required argument'); | ||
// Flexible argument magic! | ||
var args = Array.prototype.slice.call(arguments, 4); | ||
// Pluck servername. | ||
if (args[0] && typeof args[0] == 'string') | ||
servername = args.shift() || servername; | ||
// Pluck options. | ||
if (args[0] && typeof args[0] == 'object') | ||
options = args.shift() || options; | ||
// Pluck callback. | ||
if (args[0] && typeof args[0] == 'function') | ||
callback = args.shift() || callback; | ||
// Don't be messy. | ||
delete args; | ||
// We allow recipients to be passed as either a string or an array, | ||
// but normalize to to an array for consistency later in the | ||
// function. | ||
if (typeof(recipients) == 'string') | ||
recipients = [recipients]; | ||
//generate the body text | ||
var body = querystring.stringify({ | ||
sender: sender, | ||
recipients: recipients.join(', '), | ||
subject: subject, | ||
body: text, | ||
options: JSON.stringify(options) | ||
}); | ||
//prep http request | ||
var httpOptions = { | ||
host: 'mailgun.net', | ||
port: 80, | ||
method: 'POST', | ||
path: '/api/messages.txt?api_key=' + apiKey + '&servername=' + servername, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
'Content-Length': Buffer.byteLength(body) | ||
// Build the HTTP POST body text. | ||
var body = querystring.stringify({ | ||
sender: sender, | ||
recipients: recipients.join(', '), | ||
subject: subject, | ||
body: text, | ||
options: JSON.stringify(options) | ||
}); | ||
// Prepare our API request. | ||
var httpOptions = this._createHttpOptions('messages.txt', 'POST', servername); | ||
httpOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
httpOptions.headers['Content-Length'] = Buffer.byteLength(body); | ||
// Fire the request to Mailgun's API. | ||
var req = http.request(httpOptions, function(res) { | ||
// If the user supplied a callback, fire it and set `err` to the | ||
// status code of the request if it wasn't successful. | ||
if (callback) callback(res.statusCode != 201 ? new Error(res.statusCode) : undefined); | ||
}); | ||
// Wrap up the request by sending the body, which contains the | ||
// actual email data we want to send. | ||
req.end(body); | ||
}; | ||
Mailgun.prototype.sendRaw = function(sender, recipients, rawBody) { | ||
// These are flexible arguments, so we define them here to make | ||
// sure they're in scope. | ||
var servername = ''; | ||
var callback = null; | ||
// Less than 3 arguments means we're missing something that prevents | ||
// us from even sending an email, so we fail. | ||
if (arguments.length < 3) | ||
throw new Error('Missing required argument'); | ||
// Flexible argument magic! | ||
var args = Array.prototype.slice.call(arguments, 3); | ||
// Pluck servername. | ||
if (args[0] && typeof args[0] == 'string') | ||
servername = args.shift() || servername; | ||
// Pluck callback. | ||
if (args[0] && typeof args[0] == 'function') | ||
callback = args.shift() || callback; | ||
// Don't be messy. | ||
delete args; | ||
// We allow recipients to be passed as either a string or an array, | ||
// but normalize to to an array for consistency later in the | ||
// function. | ||
if (typeof(recipients) == 'string') | ||
recipients = [recipients]; | ||
// Mailgun wants its messages formatted in a special way. Why? | ||
// Who knows. | ||
var message = sender + | ||
'\n' + recipients.join(', ') + | ||
'\n\n' + rawBody; | ||
// Prepare the APi request. | ||
var httpOptions = this._createHttpOptions('messages.eml', 'POST', servername); | ||
httpOptions.headers['Content-Type'] = 'text/plain; charset=utf-8'; | ||
httpOptions.headers['Content-Length'] = Buffer.byteLength(message); | ||
// Fire it. | ||
var req = http.request(httpOptions, function(res) { | ||
// If the user supplied a callback, fire it and set `err` to the | ||
// status code of the request if it wasn't successful. | ||
if (callback) callback(res.statusCode != 201 ? new Error(res.statusCode) : undefined); | ||
}); | ||
// Wrap up the request by sending the message, which contains the | ||
// actual email data we want to send. | ||
req.end(message); | ||
}; | ||
// | ||
// Here follows the routing code | ||
// | ||
Mailgun.prototype.createRoute = function(pattern, destination, callback) { | ||
// Prep the request. | ||
var httpOptions = this._createHttpOptions('routes.xml', 'POST'); | ||
// Create the HTTP POST data. | ||
var data = '' + | ||
'<route>' + | ||
'<pattern>' + pattern + '</pattern>' + | ||
'<destination>' + destination + '</destination>' + | ||
'</route>'; | ||
// Prep the request. | ||
var httpOptions = this._createHttpOptions('routes.xml', 'POST'); | ||
httpOptions.headers['Content-Type'] = 'text/xml'; | ||
httpOptions.headers['Content-Length'] = Buffer.byteLength(data); | ||
// Fire it. | ||
http.request(httpOptions, function(res) { | ||
// Collect the data | ||
var data = ''; | ||
res.on('data', function(c) { data += c }); | ||
res.on('close', function(err) { callback(err) }); | ||
res.on('end', function() { finish() }); | ||
// Handle the results | ||
var finish = function() { | ||
if (res.statusCode == 201) { | ||
var id = xre('id').exec(data)[1]; | ||
callback(undefined, id); | ||
} else { | ||
var message = xre('message').exec(data); | ||
callback(new Error(message ? message[1] : data)); | ||
} | ||
}; | ||
}).end(data); | ||
}; | ||
//fire the request | ||
var req = http.request(httpOptions, function(res) { | ||
if (callback) callback(res.statusCode != 201); | ||
Mailgun.prototype.deleteRoute = function(id, callback) { | ||
// Prep the request | ||
var httpOptions = this._createHttpOptions('routes/' + id + '.xml', 'DELETE'); | ||
httpOptions.headers['Content-Type'] = 'text/xml'; | ||
httpOptions.headers['Content-Length'] = 0; | ||
// Fire it. | ||
http.request(httpOptions, function(res) { | ||
if (res.statusCode == 200) { | ||
callback(undefined); | ||
} else { | ||
var data = ''; | ||
res.on('data', function(c) { data += c }); | ||
res.on('close', function(err) { callback(err) }); | ||
res.on('end', function() { | ||
var message = xre('message').exec(data); | ||
callback(new Error(message ? message[1] : data)) | ||
}); | ||
} | ||
}).end(); | ||
}; | ||
Mailgun.prototype.getRoutes = function(callback) { | ||
// Prep the request. | ||
var httpOptions = this._createHttpOptions('routes.xml', 'GET'); | ||
// Fire it. | ||
http.request(httpOptions, function(res) { | ||
// Check for failure | ||
if (res.statusCode != 200) | ||
return callback(res.statusCode); | ||
// We're going to be a little lazy and just eat up all the data | ||
// before parsing it. | ||
var data = ''; | ||
res.on('data', function(c) { | ||
data += c; | ||
}); | ||
req.end(body); | ||
}; | ||
this.sendRaw = function(sender, recipients, rawBody, servername, callback) { | ||
//defaults | ||
servername = servername || ''; | ||
//be flexible with recipients | ||
if (typeof(recipients) == 'string') | ||
recipients = [recipients]; | ||
//create the message | ||
var message = sender + | ||
'\n' + recipients.join(', ') + | ||
'\n\n' + rawBody; | ||
//prep http request | ||
var httpOptions = { | ||
host: 'mailgun.net', | ||
port: 80, | ||
method: 'POST', | ||
path: '/api/messages.eml?api_key=' + apiKey + '&servername=' + servername, | ||
headers: { | ||
'Content-Type': 'text/plain; charset=utf-8', | ||
'Content-Length': Buffer.byteLength(message) | ||
// Handle catastrophic failures with an error | ||
res.on('close', function(err) { | ||
// FIXME - In some cases this could cause the callback to be called | ||
// with an error, even after we called it successfully. | ||
callback(err.code); | ||
}); | ||
// Once the request is done, we have all the data and can parse it. | ||
res.on('end', function() { | ||
// Silly XML parsing because I don't want to include another | ||
// dependency. Fortunately the structure is very simple and | ||
// convenient to parse with this method. | ||
var routes = data.replace(/\s/g, '').match(xre('route')); | ||
var nroutes = []; | ||
for (var i=0; i<routes.length; i++) { | ||
// Pull the route out, since we're going to change it. | ||
var route = routes[i]; | ||
// Pull the data. | ||
var r = {}; | ||
r.pattern = xre('pattern').exec(route)[1]; | ||
r.destination = xre('destination').exec(route)[1]; | ||
r.id = xre('id').exec(route)[1]; | ||
nroutes.push(r); | ||
} | ||
}; | ||
//fire the request | ||
var req = http.request(httpOptions, function(res) { | ||
if (callback) callback(res.statusCode != 201); | ||
// Send the data to the callback. | ||
callback(undefined, nroutes); | ||
}); | ||
req.end(message); | ||
}; | ||
}).end(); | ||
}; | ||
@@ -80,0 +316,0 @@ |
{ | ||
"name": "mailgun", | ||
"version": "0.1.6", | ||
"description": "Mailgun API for Node.js", | ||
"version": "0.4.0", | ||
"description": "Node.js interface to the Mailgun API", | ||
"author": "shz", | ||
@@ -6,0 +6,0 @@ "repositories": [{ |
162
readme.md
# node-mailgun | ||
A mailgun API for node.js. At the moment, this really only includes | ||
sending functionality. | ||
This library provides simple access to Mailgun's API for node.js applications. | ||
It's MIT licensed, and being used in production over at [Hipsell](http://hipsell.com). | ||
## Installation | ||
npm install mailgun | ||
Or you can just throw `mailgun.js` into your application. There are | ||
no dependendies outside of node's standard library. | ||
**Note:** `master` on Github is going to be untested/unstable at times, | ||
as this is a small enough library that I don't want to bother | ||
with a more complicated repo structure. As such, you should | ||
really only rely on the version of `mailgun` in `npm`, as | ||
I'll only ever push stable and tested code there. | ||
## Usage | ||
At the time of writing, Mailgun's documentation is actually incorrect in places, | ||
which is unfortunate. As such, I'm going to re-document everything in this README | ||
according to the actual way it's implemented in `node-mailgun`, which itself | ||
is based off the implementation from Mailgun's github account, and not the API | ||
docs on the site. | ||
## Initialization | ||
Access to the API is done through the Mailgun object. It's instantiated | ||
Access to the API is done through a Mailgun object. It's instantiated | ||
like so: | ||
@@ -18,7 +39,13 @@ | ||
`sendText(sender, recipients, subject, text, servername='', options={}, callback(err))` | ||
### sendText | ||
Sends a simple plain-text email. This also allows for slightly easier | ||
sending of Mailgun options, since with `sendRaw` you have to set them | ||
in the MIME body yourself. | ||
`sendText(sender, recipients, subject, text, [servername=''], [options={}], [callback(err)])` | ||
* `sender` - Sender of the message; this should be a full email address | ||
(e.g. `example@example.com). | ||
* `recipients` - An array (`['a@example.com', 'b@example.com']`) or string (`example@example.com`) | ||
(e.g. `example@example.com`). | ||
* `recipients` - A string (`example@example.com`) or array of strings (`['a@example.com', 'b@example.com']`) | ||
of recipients; these can be email addresses *or* HTTP URLs. | ||
@@ -28,3 +55,3 @@ * `subject` - Message subject | ||
* `servername` - The name of the Mailgun server. If you only have | ||
one server on your Mailgun account, this can be empty. | ||
one server on your Mailgun account, this can be omitted. | ||
Otherwise, it should be set to the server you want to | ||
@@ -37,30 +64,74 @@ send from. | ||
* `callback` - Callback to be fired when the email is done being sent. This | ||
should take a single parameter, `err`, that will be set to | ||
`true` if the email failed to send. | ||
should take a single parameter, `err`, that will be set to | ||
the status code of the API HTTP response code if the email | ||
failed to send; on success, `err` will be `undefined`. | ||
### Mailgun Headers | ||
#### Example | ||
Mailgun understands a couple headers from `options`. These are defined | ||
below. | ||
sendText('sender@example.com', | ||
['recipient1@example.com', 'http://example.com/recipient2'], | ||
'Behold the wonderous power of email!', | ||
{'X-Campaign-Id': 'something'}, | ||
function(err) { err && console.log(err) }); | ||
* `X-Mailgun-Tag` - Used to tag sent emails (defined in `Mailgun.MAILGUN_TAG`) | ||
* `X-Campaign-Id` - Used for tracking campaign data (defined in `Mailgun.CAMPAIGN_ID`) | ||
### sendRaw | ||
`sendRaw(sender, recipients, rawBody, servername, callback(err))` | ||
Sends a raw MIME message. *Don't* just use this with text; instead, | ||
you should either build a MIME message manually or by using some MIME | ||
library (I've not been able to find one for node.js -- if you're aware | ||
of one let me know and I'll link it here). | ||
`sendRaw(sender, recipients, rawBody, [servername], [callback(err)])` | ||
* `sender` - Sender of the message; this should be a full email address | ||
(e.g. `example@example.com`) | ||
* `recipients` - An array (`['a@example.com', 'b@example.com']`) or string (`example@example.com`) | ||
* `recipients` - A string (`example@example.com`) or array of strings (`['a@example.com', 'b@example.com']`) | ||
of recipients; these can be email addresses *or* HTTP URLs. | ||
* `rawBody` - MIME message to send | ||
* `servername` - The name of the Mailgun server. If you only have | ||
one server on your Mailgun account, this can be empty. | ||
one server on your Mailgun account, this can be omitted. | ||
Otherwise, it should be set to the server you want to | ||
send from. | ||
* `callback` - Callback to be fired when the email has finished sending. | ||
This function should take a single parameter, `err`, that | ||
will be set to `true` if the email failed to send. | ||
* `callback` - Callback to be fired when the email is done being sent. This | ||
should take a single parameter, `err`, that will be set to | ||
the status code of the API HTTP response code if the email | ||
failed to send; on success, `err` will be `undefined`. | ||
## Example | ||
**Note:** Sending a message via raw MIME lets you use Mailgun's built-in | ||
templating shinies. Check out the [Mailgun Docs](http://documentation.mailgun.net/Documentation/DetailedDocsAndAPIReference#Message_Templates) | ||
for details. | ||
#### Example | ||
sendRaw('sender@example.com', | ||
['recipient1@example.com', 'http://example.com/recipient2'], | ||
'From: sender@example.com' + | ||
'\nTo: ' + 'recipient1@example.com, http://example.com/recipient2' + | ||
'\nContent-Type: text/html; charset=utf-8' + | ||
'\nSubject: I Love Email' + | ||
'\n\nBecause it's just so awesome', | ||
function(err) { err && console.log(err) }); | ||
### Email Addresses | ||
Mailgun allows sender and recipient email addresses to be formatted in | ||
several different ways: | ||
* `'John Doe' <john@example.com>` | ||
* `"John Doe" <john@example.com>` | ||
* `John Doe <john@example.com>` | ||
* `<john@example.com>` | ||
* `john@example.com` | ||
### Mailgun Headers | ||
Mailgun understands a couple special headers, specified via `options` when using | ||
`sendText`, or in the MIME headers when using `sendRaw`. These are defined | ||
below. | ||
* `X-Mailgun-Tag` - Used to tag sent emails (defined in `Mailgun.MAILGUN_TAG`) | ||
* `X-Campaign-Id` - Used for tracking campaign data (defined in `Mailgun.CAMPAIGN_ID`) | ||
### Example | ||
Here's a complete sending example. | ||
@@ -70,4 +141,4 @@ | ||
var mg = new Mailgun('api-key'); | ||
mg.sendText('example@example.com', ['rec1@example.com', 'rec2@example.com'], | ||
var mg = new Mailgun('some-api-key'); | ||
mg.sendText('example@example.com', ['Recipient 1 <rec1@example.com>', 'rec2@example.com'], | ||
'This is the subject', | ||
@@ -77,10 +148,47 @@ 'This is the text', | ||
function(err) { | ||
if (err) console.log('Oh noes'); | ||
if (err) console.log('Oh noes: ' + err); | ||
else console.log('Success'); | ||
}); | ||
## TODO: | ||
## Routing | ||
* routes | ||
* mailboxes | ||
Mailgun lets you route incoming email to different destinations. TODO - more docs | ||
### createRoute | ||
TODO | ||
### deleteRoute | ||
Deletes a route if it exists, otherwise fails silently. | ||
### getRoutes | ||
Gets a list of all routes. | ||
`getRoutes(callback(err, routes))` | ||
* `callback` - Callback to be fired when the request has finished. This | ||
should take two parameters: `err`, which will hold either an | ||
HTTP error code, or an error string on failure; and `routes`, | ||
which will be a list of routes on success. Routes returned | ||
through this callback will be objects with three fields: `pattern`, | ||
`destination`, and `id`. | ||
#### Example | ||
getRoutes(function(err, routes) { | ||
if (err) console.log('Error:', err); | ||
for (var i=0; i<routes.length; i++) { | ||
console.log('Route'); | ||
console.log(' Pattern:', routes[i].pattern); | ||
console.log(' Destination:', routes[i].destination); | ||
console.log(' Id:', routes[i].id); | ||
} | ||
}); | ||
## Eventual Work: | ||
* Mailboxes | ||
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
17481
5
260
190
2