diff --git a/README.md b/README.md
new file mode 100644
index 0000000..889e9e9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,338 @@
+# Postmark API
+
+This is a full REST API wrapper for Postmark
+
+To use the module, run `npm install postmarkapi`
+
+## Using the module
+
+First you must make a instance of the module, with your Postmark Server token.
+
+```js
+var PostmarkAPI = require('postmarkapi');
+var pmk = new PostmarkAPI('[your server token]');
+```
+
+## Sending an email
+
+You always need to define a `to` for an email, and `subject`. You can send `text` or `html`, or both, but you need to define one or ther other.
+
+If you don't define a `from`, it will use the address you created the Sender Signature with.
+
+You can send multiple `cc`s or `bcc`s by passing an array.
+
+You don't need to pass a callback.
+
+```js
+// simple example
+pmk.email({
+ to: 'someaddress@somewhere.com',
+ subject: 'Test Email',
+ text: 'Hello World'
+});
+
+// more specific
+pmk.email({
+ to: 'someaddress@somewhere.com',
+ from: 'fromaddress@somewhere.com',
+ fromName: 'John Doe',
+ cc: ['carbon-copy@somewhere.com', 'carbon-copy-2@somewhere.com'],
+ bcc: 'blind-carbon-copy@somewhere.com',
+ reply: 'reply-to@somewhere.com',
+ tag: 'MyTag',
+ headers: {
+ EmailedUsing: 'Node PostmarkAPI Module'
+ },
+ subject: 'Test Email',
+ text: 'Hello World',
+ html: 'Hello World'
+}, function(err, response) {
+ // ...
+});
+```
+You can also send an email with attachments
+
+```js
+var path = require('path');
+
+pmk.email({
+ to: 'someaddress@somewhere.com',
+ from: 'fromaddress@somewhere.com',
+ subject: 'Test Email',
+ text: 'Hello World',
+ html: 'Hello World',
+ attachments: [
+ path.resolve(__dirname, 'cats.gif'),
+ path.resolve(__dirname, 'notes.txt')
+ ]
+}, function(err, response) {
+ // ...
+});
+```
+
+## Bounces
+
+### Getting a summary of bounces for the server
+
+```js
+pmk.deliverystats(function(err, response) {});
+```
+
+### Retrieving bounces
+
+You can retrieve bounces associated with your server.
+
+```js
+// simple example
+pmk.bounces({
+ count: 10,
+ offset: 0
+}, function(err, response) {});
+
+// with messageId
+pmk.bounces({
+ messageId: '[messageIDHere]'
+}, function(err, response) {});
+
+// more sepcific
+pmk.bounces({
+ count: 10,
+ offset: 0,
+ type: 'HardBounce',
+ inactive: 0,
+ emailFilter: 'somewhere.com'
+}, function(err, response) {});
+```
+
+### Getting a list of tags for bounces on server
+
+```js
+pmk.bounceTags(function(err, response) {});
+```
+
+### Getting a single bounce
+
+```js
+pmk.bounce(bouncId, function(err, response) {});
+```
+
+### Getting a single bounce's dump
+
+```js
+pmk.bounceDump(bouncId, function(err, response) {});
+```
+
+### Activating a deactivated bounce
+
+Callback optional
+
+```js
+pmk.bounceActivate(bounceId, function(err, response) {});
+```
+
+## Outbound messages
+
+### Retrieving sent messages
+
+```js
+// simple example
+pmk.outbound({
+ count: 10,
+ offset: 0
+}, function(err, response) {});
+```
+
+```js
+// more specific
+pmk.outbound({
+ count: 10,
+ offset: 0,
+ recipient: 'someone@somewhere.com',
+ fromemail: 'fromemail@somewhere.com',
+ tag: 'MyTag',
+ subject: 'Welcome Email'
+}, function(err, response) {});
+```
+
+### Getting details for a single sent message
+
+```js
+pmk.outboundMessage(messageId, function(err, response) {});
+```
+
+### Getting message dump
+
+```js
+pmk.outboundMessageDump(messageId, function(err, response) {});
+```
+
+## Inbound messages
+
+### Retrieving recieved messages
+
+```js
+// simple example
+pmk.inbound({
+ count: 10,
+ offset: 0
+}, function(err, response) {});
+```
+
+```js
+// more specific
+pmk.inbound({
+ count: 10,
+ offset: 0,
+ recipient: 'someone@somewhere.com',
+ fromemail: 'fromemail@somewhere.com',
+ tag: 'MyTag',
+ subject: 'Welcome Email',
+ mailboxhash: 'mailboxhashvalue'
+}, function(err, response) {});
+```
+
+### Gettings details for a single recieved message
+
+```js
+pmk.inboundMessage(messageId, function(err, response) {});
+```
+
+## Sender Signatures
+
+### Getting a list of Sender Signatures
+
+```js
+pmk.senders({
+ count: 10,
+ offset: 0
+}, function(err, response) {});
+```
+
+### Fetching details for a single sender
+
+```js
+pmk.sender(senderId, function(err, response) {});
+```
+
+### Creating a Sender Signature
+
+The `reply` and callback are optional
+
+```js
+pmk.createSender({
+ name: 'Sender Name',
+ from: 'senderemail@somewhere.com',
+ reply: 'replyto@somewhere.com'
+}, function(err, response) {});
+```
+
+### Updating a Sender Signature
+
+The `reply` and callback are optional
+
+You cannot update the `from` address
+
+```js
+pmk.updateSender(senderId, {
+ name: 'Sender Name',
+ reply: 'replyto@somewhere.com'
+}, function(err, response) {});
+```
+
+### Resending a Sender Signature confirmation email
+
+The callback is optional
+
+```js
+pmk.resendSender(senderId, function(err, response) {});
+```
+
+### Deleting a Sender Signature
+
+The callback is optional
+
+```js
+pmk.deleteSender(senderId, function(err, response) {});
+```
+
+### Verifying a SPF record
+
+The callback is optional
+
+```js
+pmk.verifySPF(senderId, function(err, response) {});
+```
+
+### Requesting a new DKIM
+
+The callback is optional
+
+```js
+pmk.requestDKIM(senderId, function(err, response) {});
+```
+
+## Servers
+
+### Getting a list of servers
+
+```js
+pmk.servers({
+ count: 10,
+ offset: 0,
+ name: 'Production'
+}, function(err, response) {});
+```
+
+### Getting a single server's details
+
+```js
+pmk.server(serverId, function(err, response) {});
+```
+
+### Creating a new server
+
+```js
+// simple example
+pmk.createServer({
+ name: 'Server Name'
+});
+
+// more specific
+pmk.createServer({
+ name: 'Server Name',
+ color: 'red',
+ smtp: true,
+ raw: true,
+ inboundHook: 'https://...',
+ bounceHook: 'https://...',
+ inboundDomain: 'myDomain'
+}, function(err, response) {});
+```
+
+### Updating a server
+
+```js
+// simple example
+pmk.updateServer(serverId, {
+ name: 'Server Name'
+});
+
+// more specific
+pmk.updateServer(serverId, {
+ name: 'Server Name',
+ color: 'red',
+ smtp: true,
+ raw: true,
+ inboundHook: 'https://...',
+ bounceHook: 'https://...',
+ inboundDomain: 'myDomain'
+}, function(err, response) {});
+```
+
+### Deleting a server
+
+The callback is optional
+
+```js
+pmk.deleteServer(serverId, function(err, response) {});
+```
diff --git a/package.json b/package.json
index d2717ca..b804c99 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
"node": ">=0.10.20"
},
"dependencies" : {
- "request": "2.34.0"
+ "request": "2.34.0",
+ "mime": "1.2.11",
+ "async": "0.7.0"
},
"repository": "git://github.com/MadisonReed/postmarkapi"
}
\ No newline at end of file
diff --git a/src/pmk.js b/src/pmk.js
index 05a9d27..ec82b87 100644
--- a/src/pmk.js
+++ b/src/pmk.js
@@ -1,5 +1,9 @@
// external dependencies
var request = require('request');
+var async = require('async');
+var fs = require('fs');
+var path = require('path');
+var mime = require('mime');
// internal dependencies
var PMKError = require('./error.js');
@@ -7,6 +11,7 @@ var PMKError = require('./error.js');
// globals
var nonWord = /[^a-z0-9 ]/i;
var noOp = function() {};
+var slice = Array.prototype.slice;
/**
Constructor for the API
@@ -18,7 +23,10 @@ function PMK(token) {
throw 'You must initialize with a valid postmark api token';
}
- this.token = token;
+ this.get = curry(makeRequest, token, 'GET');
+ this.post = curry(makeRequest, token, 'POST');
+ this.put = curry(makeRequest, token, 'PUT');
+ this.delete = curry(makeRequest, token, 'DELETE');
}
/**
@@ -32,13 +40,17 @@ function PMK(token) {
@param {String|String[]} message.to Email recipient(s)
@param {String|String[]} [message.cc] Carbon copy recipient(s)
@param {String|String[]} [message.bcc] Blind carbon copy recipient(s)
- @param {String|String[]} [message.reply] Reply To email address
+ @param {String|String[]} [message.reply] Reply-To email address
+ @param {String} [message.tag] Tag
+ @param {Object} [message.headers] Custom headers to send in request (key value pairs)
+ @param {String[]} Attachments (string paths to local files)
@param {String} message.subject Subject
@param {String} [message.text] Text body
@param {String} [message.html] HTML body
@param {Function} [callback] Callback function
*/
PMK.prototype.email = function(message, callback) {
+ var self = this;
var body = {};
var postmarkKey, key;
var recipients = 0;
@@ -107,35 +119,455 @@ PMK.prototype.email = function(message, callback) {
return;
}
- // rest of the keys
+ if (message.headers) {
+ body.Headers = [];
+
+ for (key in message.headers) {
+ body.Headers.push({
+ Name: key,
+ Value: message.headers[key]
+ });
+ }
+ }
+
+ if (message.attachments) {
+ body.Attachments = [];
+
+ async.each(message.attachments, function(filepath, cb) {
+ if (typeof filepath !== 'string') {
+ cb(new PMKError('Attachments must be file paths'));
+ return;
+ }
+
+ fs.readFile(filepath, function(err, content) {
+ if (err) {
+ cb(err);
+ return;
+ }
+
+ body.Attachments.push({
+ Name: path.basename(filepath),
+ Content: content.toString('base64'),
+ ContentType: mime.lookup(filepath)
+ });
+ cb();
+ });
+ }, doRequest);
+ } else {
+ doRequest();
+ }
+
+ function doRequest(err) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ // rest of the keys
+ var pairs = {
+ reply: 'ReplyTo',
+ tag: 'Tag',
+ html: 'HtmlBody',
+ text: 'TextBody',
+ subject: 'Subject',
+ to: 'To'
+ }
+ for (key in pairs) {
+ if (message[key]) {
+ body[ pairs[key] ] = message[key];
+ }
+ }
+
+ // finally, the request
+ self.post('email', body, callback);
+ }
+};
+
+/**
+ Returns a summary of inactive emails and bounces by type over the entire history of the server
+
+ @param {Function} callback Callback function
+*/
+PMK.prototype.deliverystats = function(callback) {
+ this.get('deliverystats', callback);
+};
+
+/**
+ Retrieves bounces
+
+ @param {Object} options Options for the request
+ @param {String|Number} [options.count] Count for paging [Required if not passing messageID]
+ @param {String|Number} [options.offset] Offset for paging [Required if not passing messageID]
+ @param {String} [options.type] Bounce type
+ @param {Boolean} [options.inactive] Filter by inactive / active status
+ @param {String} [options.emailFilter] Filters out emails that don't match this substring
+ @param {String} [options.messageID] Returns only messages matching the given message id
+ @param {Function} callback Callback function
+*/
+PMK.prototype.bounces = function(options, callback) {
+ this.get('bounces', options, callback);
+};
+
+/**
+ Gets a single bounce
+
+ @param {String} id The bounce id
+ @param {Function} callback Callback function
+*/
+PMK.prototype.bounce = function(id, callback) {
+ this.get('bounces/' + id, callback);
+};
+
+/**
+ Returns a single bounce's dump
+
+ @param {String} id The bounce id
+ @param {Function} callback Callback function
+*/
+PMK.prototype.bounceDump = function(id, callback) {
+ this.get('bounces/' + id + '/dump', callback);
+};
+
+/**
+ Returns a list of tags used for the current server.
+
+ @param {Function} callback Callback function
+*/
+PMK.prototype.bounceTags = function(callback) {
+ this.get('bounces/tags', callback);
+};
+
+/**
+ Activates a deactivated bounce
+
+ @param {String} id The bounce id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.bounceActivate = function(id, callback) {
+ callback = callback || noOp;
+
+ this.put('bounces/' + id + '/active', callback);
+};
+
+/**
+ Gets sent messages
+
+ @param {Object} options The filtering options
+ @param {String|Number} options.count Paging count
+ @param {String|Number} options.offset Paging offset
+ @param {String} [options.recipient] Who the message was sent to
+ @param {String} [options.fromemail] Messages with a given 'from' email address
+ @param {String} [options.tag] Messages with a given tag
+ @param {String} [options.subject] Messages with a given subject
+ @param {Function} callback Callback function
+*/
+PMK.prototype.outbound = function(options, callback) {
+ this.get('messages/outbound', options, callback);
+};
+
+/**
+ Get details for a single sent message
+
+ @param {String} id The message id for the email
+ @param {Function} callback Callback function
+*/
+PMK.prototype.outboundMessage = function(id, callback) {
+ this.get('messages/outbound/' + id + '/details', callback);
+};
+
+/**
+ Get sent email dump
+
+ @param {String} id The message id for the email
+ @param {Function} callback Callback function
+*/
+PMK.prototype.outboundMessageDump = function(id, callback) {
+ this.get('messages/outbound/' + id + '/dump', callback);
+};
+
+/**
+ Gets recieved messages
+
+ @param {Object} options The filtering options
+ @param {String|Number} options.count Paging count
+ @param {String|Number} options.offset Paging offset
+ @param {String} [options.recipient] Who the message was sent to
+ @param {String} [options.fromemail] Messages with a given 'from' email address
+ @param {String} [options.tag] Messages with a given tag
+ @param {String} [options.subject] Messages with a given subject
+ @param {String} [options.mailboxhash] Messages with a given mailboxhash
+ @param {Function} callback Callback function
+*/
+PMK.prototype.inbound = function(options, callback) {
+ this.get('messages/inbound', options, callback);
+};
+
+/**
+ Get details for a single recieved message
+
+ @param {String} id The message id for the email
+ @param {Function} callback Callback function
+*/
+PMK.prototype.inboundMessage = function(id, callback) {
+ this.get('messages/inbound/' + id + '/details', callback);
+};
+
+/**
+ Fetches a list of Sender Signatures
+
+ @param {Object} options The paging options
+ @param {String|Number} options.count Paging count
+ @param {String|Number} options.offset Paging offset
+ @param {Function} callback Callback function
+*/
+PMK.prototype.senders = function(options, callback) {
+ this.get('senders', options, callback);
+};
+
+/**
+ Fetches a single Sender's details
+
+ @param {String} id The Sender id
+ @param {Function} callback Callback function
+*/
+PMK.prototype.sender = function(id, callback) {
+ this.get('senders/' + id, callback);
+};
+
+/**
+ Creates a new Sender Signature
+
+ @param {Object} options The creation options
+ @param {String} options.name The name of the sender
+ @param {String} options.from The from email address
+ @param {String} [options.reply] The reply-to email address
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.createSender = function(options, callback) {
+ callback = callback || noOp;
+
+ var body = {
+ Name: options.name,
+ FromEmail: options.from
+ };
+
+ if (options.reply) {
+ body.ReplyToEmail = options.reply;
+ }
+
+ this.post('senders', body, callback);
+};
+
+/**
+ Updates a Sender Signature
+
+ @param {String} id The sender id
+ @param {Object} options The update options
+ @param {String} options.name The name of the sender
+ @param {String} [options.reply] The reply-to email address
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.updateSender = function(id, options, callback) {
+ callback = callback || noOp;
+
+ var body = {
+ Name: options.name
+ };
+
+ if (options.reply) {
+ body.ReplyToEmail = options.reply;
+ }
+
+ this.put('senders/' + id, body, callback);
+};
+
+/**
+ Resends confirmation email for a Sender Signature
+
+ @param {String} id The sender id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.resendSender = function(id, callback) {
+ callback = callback || noOp;
+
+ this.post('senders/' + id + '/resend', callback);
+};
+
+/**
+ Deletes a Sender Signature
+
+ @param {String} id The sender id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.deleteSender = function(id, callback) {
+ callback = callback || noOp;
+
+ this.delete('sender/' + id, callback);
+};
+
+/**
+ Verifies a SPF record
+
+ @param {String} id The sender id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.verifySPF = function(id, callback) {
+ callback = callback || noOp;
+
+ this.post('senders/' + id + '/verifyspf', callback);
+};
+
+/**
+ Requests a new DKIM
+
+ @param {String} id The sender id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.requestDKIM = function(id, callback) {
+ callback = callback || noOp;
+
+ this.post('senders/' + id + '/requestnewdkim', callback);
+};
+
+/**
+ Lists servers
+
+ @param {Object} options The listing options
+ @param {String|Number} options.count Paging count
+ @param {String|Number} options.offset Paging offset
+ @param {String} options.name Server name to search by (.e.g 'production')
+ @param {Function} callback Callback function
+*/
+PMK.prototype.servers = function(options, callback) {
+ this.get('servers', options, callback);
+};
+
+/**
+ Gets a single server's details
+
+ @param {String} id The server's id
+ @param {Function} callback Callback function
+*/
+PMK.prototype.server = function(id, callback) {
+ this.get('servers/' + id, callback);
+};
+
+/**
+ Creates a new server
+
+ @param {Object} options The creation options
+ @param {String} options.name The name of the server
+ @param {String} [options.color] The color indicator (e.g. 'red')
+ @param {Boolean} [options.smtp] Indicate if this Server should have SMTP access turned on
+ @param {Boolean} [options.raw] Indicate if Inbound web hook http post calls should include the original RAW email in the JSON body
+ @param {String} [options.inboundHook] Url to send http posts to for Inbound message processing
+ @param {String} [options.bounceHook] Url to send http posts to for any message bounces that occur on this Server
+ @param {String} [options.inboundDomain] The MX domain used for MX Inbound processing
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.createServer = function(options, callback) {
+ callback = callback || noOp;
+
+ var body = {
+ Name: options.name
+ };
+
var pairs = {
- reply: 'ReplyTo',
- tag: 'Tag',
- html: 'HtmlBody',
- text: 'TextBody',
- headers: 'Headers', // to do: make this one better
- subject: 'Subject',
- to: 'To',
- attachments: 'Attachments' // to do: make this one better
- }
- for (key in pairs) {
- if (message[key]) {
- body[ pairs[key] ] = message[key];
+ color: 'Color',
+ smtp: 'SmtpApiActivated',
+ raw: 'RawEmailEnabled',
+ inboundHook: 'InboundHookUrl',
+ bounceHook: 'BounceHookUrl',
+ inboundDomain: 'InboundDomain'
+ };
+
+ for (var key in pairs) {
+ if (options[key]) {
+ body[ pars[key] ] = options[key];
+ }
+ }
+
+ this.post('servers', body, callback);
+};
+
+/**
+ Edits an existing server
+
+ @param {String} id The server id
+ @param {Object} options The listing options
+ @param {String} options.name The name of the server
+ @param {String} [options.color] The color indicator (e.g. 'red')
+ @param {Boolean} [options.smtp] Indicate if this Server should have SMTP access turned on
+ @param {Boolean} [options.raw] Indicate if Inbound web hook http post calls should include the original RAW email in the JSON body
+ @param {String} [options.inboundHook] Url to send http posts to for Inbound message processing
+ @param {String} [options.bounceHook] Url to send http posts to for any message bounces that occur on this Server
+ @param {String} [options.inboundDomain] The MX domain used for MX Inbound processing
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.updateServer = function(id, options, callback) {
+ callback = callback || noOp;
+
+ var body = {};
+
+ var pairs = {
+ name: 'Name',
+ color: 'Color',
+ smtp: 'SmtpApiActivated',
+ raw: 'RawEmailEnabled',
+ inboundHook: 'InboundHookUrl',
+ bounceHook: 'BounceHookUrl',
+ inboundDomain: 'InboundDomain'
+ };
+
+ for (var key in pairs) {
+ if (options[key]) {
+ body[ pars[key] ] = options[key];
}
}
- // finally, the request
- request({
- method: 'POST',
- uri: 'https://api.postmarkapp.com/email',
+ this.put('servers/' + id, body, callback);
+};
+
+/**
+ Deletes an existing server
+
+ @param {String} id The server id
+ @param {Function} [callback] Callback function
+*/
+PMK.prototype.deleteServer = function(id, callback) {
+ callback = callback || noOp;
+
+ this.delete('servers/' + id, callback);
+};
+
+// method to make requests to the postmark api
+// data is optional (becomes qs or body)
+function makeRequest(token, method, pathname, data, callback) {
+ if (arguments.length < 5) {
+ callback = data;
+ data = null;
+ }
+
+ var options = {
+ method: method,
+ uri: 'https://api.postmarkapp.com/' + pathname,
headers: {
charset: 'utf-8',
Accept: 'application/json',
'Content-Type': 'application/json',
- 'X-Postmark-Server-Token': this.token
- },
- body: JSON.stringify(body)
- }, function(err, res, body) {
+ 'X-Postmark-Server-Token': token
+ }
+ };
+
+ if (data) {
+ if (method.toUpperCase() === 'POST') {
+ options.body = JSON.stringify(data);
+ } else {
+ options.qs = data;
+ }
+ }
+
+ request(options, function(err, res, body) {
if (err) {
callback(err);
return;
@@ -150,9 +582,29 @@ PMK.prototype.email = function(message, callback) {
return;
}
- // to do: deal with errors in response
+ if (result.ErrorCode) {
+ if (result.Message) {
+ err = result.Message;
+ delete result.Message;
+ } else {
+ err = 'Failed';
+ }
+ callback(new PMKError(err, result));
+ return;
+ }
+
callback(null, result);
});
-};
+}
+
+// curry utility
+function curry(fn) {
+ var args = slice.call(arguments, 1);
+
+ return function() {
+ // keeping the 'this' of this function, which will be useful for prototype methods
+ return fn.apply(this, args.concat(slice.call(arguments)));
+ };
+}
module.exports = PMK;