From 9dffdfabb2f8902263fded3b521981c59a086c3d Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Wed, 14 Dec 2016 15:57:16 -0500 Subject: [PATCH 01/32] configuration for tests --- Gruntfile.js | 8 ++++---- secrets_example.json | 5 +++-- tasks/grunt-postmark.js | 14 +++++++------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 89c8ebb..454f711 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,14 +15,14 @@ module.exports = function(grunt) { serverToken: '<%= secrets.serverToken %>' }, email: { - from: 'you@youremail.com', - to: 'you@youremail.com', + from: '<%= secrets.testEmailSender %>', + to: '<%= secrets.testEmailSender %>', subject: 'Yo', src: ['test/email.html'] }, bulk: { - from: 'you@youremail.com', - to: 'you@youremail.com', + from: '<%= secrets.testEmailSender %>', + to: '<%= secrets.testEmailSender %>', subject: 'Hey', src: ['test/*.html'] } diff --git a/secrets_example.json b/secrets_example.json index a108905..bc2a8e2 100644 --- a/secrets_example.json +++ b/secrets_example.json @@ -1,3 +1,4 @@ { - "server_token": "POSTMARK_API_TEST" -} \ No newline at end of file + "serverToken": "POSTMARK_API_TEST", + "testEmailSender": "you@youremail.com", +} diff --git a/tasks/grunt-postmark.js b/tasks/grunt-postmark.js index ccf7f8b..e29e81a 100644 --- a/tasks/grunt-postmark.js +++ b/tasks/grunt-postmark.js @@ -3,10 +3,10 @@ * https://github.com/wildbit/grunt-postmark.git */ -'use strict'; - module.exports = function(grunt) { + 'use strict'; + grunt.registerMultiTask('postmark', 'Send emails through Postmark', function() { var done = this.async(); @@ -14,7 +14,7 @@ module.exports = function(grunt) { var _data = this.data; // Check for server token - if (!options.serverToken && !_data.serverToken) { + if (!options.serverToken && !_data.serverToken) { grunt.fail.warn('Missing Postmark server token \n'); } @@ -25,8 +25,8 @@ module.exports = function(grunt) { if (this.files.length > 0) { var message = { - 'From': _data.from || options.from, - 'To': _data.to || options.to, + 'From': _data.from || options.from, + 'To': _data.to || options.to, 'Subject': _data.subject || options.subject }; @@ -56,7 +56,7 @@ module.exports = function(grunt) { handleResponse(err, response, done); }); } - + } else { // Warn about no files being passed to task grunt.log.warn('No src file found \n'); @@ -66,7 +66,7 @@ module.exports = function(grunt) { function handleResponse(err, response, done) { - err ? errorMessage(err) : successMessage(response); + var _ = err ? errorMessage(err) : successMessage(response); done(); } From abb4d27909f4ae85582353de85c732bec566ab28 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Wed, 14 Dec 2016 16:48:54 -0500 Subject: [PATCH 02/32] more test config cleanup --- Gruntfile.js | 8 ++++---- secrets_example.json | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 454f711..0544cb5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,14 +15,14 @@ module.exports = function(grunt) { serverToken: '<%= secrets.serverToken %>' }, email: { - from: '<%= secrets.testEmailSender %>', - to: '<%= secrets.testEmailSender %>', + from: '<%= secrets.testSender %>', + to: '<%= secrets.testRecipient %>', subject: 'Yo', src: ['test/email.html'] }, bulk: { - from: '<%= secrets.testEmailSender %>', - to: '<%= secrets.testEmailSender %>', + from: '<%= secrets.testSender %>', + to: '<%= secrets.testRecipient %>', subject: 'Hey', src: ['test/*.html'] } diff --git a/secrets_example.json b/secrets_example.json index bc2a8e2..1cc375a 100644 --- a/secrets_example.json +++ b/secrets_example.json @@ -1,4 +1,5 @@ { "serverToken": "POSTMARK_API_TEST", - "testEmailSender": "you@youremail.com", + "testSender": "you@youremail.com", + "testRecipient": "you@youremail.com" } From 86c4d292b8b0957bf43808a2a0d512afc9f27f85 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Wed, 14 Dec 2016 17:11:46 -0500 Subject: [PATCH 03/32] basic grunt task implemented --- Gruntfile.js | 14 +++++++- tasks/grunt-postmark-templates.js | 55 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 tasks/grunt-postmark-templates.js diff --git a/Gruntfile.js b/Gruntfile.js index 0544cb5..c8a797a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -26,12 +26,24 @@ module.exports = function(grunt) { subject: 'Hey', src: ['test/*.html'] } + }, + + "postmark-templates": { + options: { + serverToken: '<%= secrets.serverToken %>', + }, + build: { + name: "testing-template-node-js" + Date(), + subject: "{{subject}}", + textBody: "text body for template {{id}}!", + htmlBody: "{{content}}", + } } }); grunt.loadTasks('tasks'); - grunt.registerTask('default', ['postmark']); + grunt.registerTask('default', ['postmark', 'postmark-templates']); }; diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js new file mode 100644 index 0000000..3589def --- /dev/null +++ b/tasks/grunt-postmark-templates.js @@ -0,0 +1,55 @@ +/* + * grunt-postmark + * https://github.com/wildbit/grunt-postmark.git + */ + +module.exports = function(grunt) { + + 'use strict'; + + grunt.registerMultiTask('postmark-templates', 'Push templates', function() { + + var done = this.async(); + var options = this.options(); + var _data = this.data; + + // Check for server token + if (!options.serverToken && !_data.serverToken) { + grunt.fail.warn('Missing Postmark server token \n'); + } + + // Postmark lib + var postmark = require('postmark'); + var client = new postmark.Client(options.serverToken || _data.serverToken); + + client.createTemplate({ + name: _data.name || options.name, + textBody: _data.textBody || options.textBody, + htmlBody: _data.htmlBody || options.htmlBody, + subject: _data.subject || options.subject, + }, function(err, response) { + handleResponse(err, response, done); + }); + + }); + + function handleResponse(err, response, done) { + if (err){ + errorMessage(err); + done(); + } else { + var templateId = response.TemplateId; + successMessage(templateId); + done(templateId); + } + } + + function errorMessage(response) { + grunt.log.warn('Error response: ' + JSON.stringify(response)); + } + + function successMessage(templateId) { + grunt.log.writeln('Template pushed: ' + JSON.stringify(templateId)); + } + +}; From e8ae0241ee24522dd10c086441672fe541e5b4e9 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Thu, 15 Dec 2016 15:41:14 -0500 Subject: [PATCH 04/32] read template files from filesystem --- Gruntfile.js | 9 +++++---- tasks/grunt-postmark-templates.js | 29 +++++++++++++++++++++++++---- test/email.txt | 1 + 3 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 test/email.txt diff --git a/Gruntfile.js b/Gruntfile.js index c8a797a..d55371a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,10 +33,11 @@ module.exports = function(grunt) { serverToken: '<%= secrets.serverToken %>', }, build: { - name: "testing-template-node-js" + Date(), - subject: "{{subject}}", - textBody: "text body for template {{id}}!", - htmlBody: "{{content}}", + name: "testing-template-node-js-" + new Date().valueOf(), + subject: "Testing grunt-postmark-templates", + // NOTE these are read from filesystem. globbing not supported + htmlFile: 'test/email.html', + textFile: 'test/email.txt', } } diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 3589def..12c3b26 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -1,5 +1,5 @@ /* - * grunt-postmark + * grunt-postmark-templates * https://github.com/wildbit/grunt-postmark.git */ @@ -15,17 +15,32 @@ module.exports = function(grunt) { // Check for server token if (!options.serverToken && !_data.serverToken) { - grunt.fail.warn('Missing Postmark server token \n'); + grunt.fail.warn('Missing required option "serverToken" \n'); } + // Check for required attributes + ['name', 'subject'].forEach(function(name){ + requiredProperty(name, options, _data); + }); + + grunt.log.writeln('Template data: ' + JSON.stringify(_data)); + // Postmark lib var postmark = require('postmark'); var client = new postmark.Client(options.serverToken || _data.serverToken); + var htmlBody = _data.htmlBody || options.htmlBody; + var textBody = _data.textBody || options.textBody; + if (_data.htmlFile) { + htmlBody = grunt.file.read(_data.htmlFile); + } + if (_data.textFile) { + textBody = grunt.file.read(_data.textFile); + } client.createTemplate({ name: _data.name || options.name, - textBody: _data.textBody || options.textBody, - htmlBody: _data.htmlBody || options.htmlBody, + textBody: textBody, + htmlBody: htmlBody, subject: _data.subject || options.subject, }, function(err, response) { handleResponse(err, response, done); @@ -33,6 +48,12 @@ module.exports = function(grunt) { }); + function requiredProperty(name, options, data) { + if (!data[name] && !options[name]) { + grunt.fail.warn('Missing required property "' + name + '" \n'); + } + } + function handleResponse(err, response, done) { if (err){ errorMessage(err); diff --git a/test/email.txt b/test/email.txt new file mode 100644 index 0000000..e5494a8 --- /dev/null +++ b/test/email.txt @@ -0,0 +1 @@ +Hello from grunt-postmark-templates From 4057b64cf777147058dfcdd9271bdef85a66e38c Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Thu, 15 Dec 2016 15:42:52 -0500 Subject: [PATCH 05/32] remove debug --- tasks/grunt-postmark-templates.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 12c3b26..6223d04 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -23,8 +23,6 @@ module.exports = function(grunt) { requiredProperty(name, options, _data); }); - grunt.log.writeln('Template data: ' + JSON.stringify(_data)); - // Postmark lib var postmark = require('postmark'); var client = new postmark.Client(options.serverToken || _data.serverToken); From d988011c35b9a1d7d1d687020d7b3cb6b36dc73e Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Thu, 15 Dec 2016 15:44:32 -0500 Subject: [PATCH 06/32] TODO for validating htmlBody/htmlFile etc. --- tasks/grunt-postmark-templates.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 6223d04..a35b8cc 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -19,8 +19,9 @@ module.exports = function(grunt) { } // Check for required attributes + // TODO: require one of fooBody or fooFile ['name', 'subject'].forEach(function(name){ - requiredProperty(name, options, _data); + requireProperty(name, options, _data); }); // Postmark lib @@ -46,7 +47,7 @@ module.exports = function(grunt) { }); - function requiredProperty(name, options, data) { + function requireProperty(name, options, data) { if (!data[name] && !options[name]) { grunt.fail.warn('Missing required property "' + name + '" \n'); } From aac24c87d920e3ae427544df057bc067e7f95518 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Thu, 15 Dec 2016 15:53:03 -0500 Subject: [PATCH 07/32] pretty good support for creating a server --- Gruntfile.js | 17 +++++++-- tasks/grunt-postmark-servers.js | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tasks/grunt-postmark-servers.js diff --git a/Gruntfile.js b/Gruntfile.js index d55371a..3a91364 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -39,12 +39,25 @@ module.exports = function(grunt) { htmlFile: 'test/email.html', textFile: 'test/email.txt', } - } + }, + + "postmark-servers": { + options: { + accountToken: '<%= secrets.accountToken %>', + }, + build: { + name: "testing-server-" + new Date().valueOf(), + smtpApiActivated: true, + + // NOTE complete list of server attributes: + // http://developer.postmarkapp.com/developer-api-servers.html#create-server + } + }, }); grunt.loadTasks('tasks'); - grunt.registerTask('default', ['postmark', 'postmark-templates']); + grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers']); }; diff --git a/tasks/grunt-postmark-servers.js b/tasks/grunt-postmark-servers.js new file mode 100644 index 0000000..c493945 --- /dev/null +++ b/tasks/grunt-postmark-servers.js @@ -0,0 +1,63 @@ +/* + * grunt-postmark-servers + * https://github.com/wildbit/grunt-postmark.git + */ + +module.exports = function(grunt) { + + 'use strict'; + + grunt.registerMultiTask('postmark-servers', 'Create server', function() { + + var done = this.async(); + var options = this.options(); + var _data = this.data; + + // Check for account token + if (!options.accountToken && !_data.accountToken) { + grunt.fail.warn('Missing option "accountToken" \n'); + } + + // Postmark lib + var postmark = require('postmark'); + var client = new postmark.AdminClient(options.accountToken || _data.accountToken); + + // Check for server name + if (!_data.name && !options.name) { + grunt.fail.warn('Missing required server property "name" \n'); + } + + client.createServer({ + name: _data.name || options.name, + color: _data.color || options.color || "Turquoise", + smtpApiActivated: _data.smtpApiActivated || options.smtpApiActivated || true, + trackOpens: _data.trackOpens || options.trackOpens || false, + trackLinks: _data.trackLinks || options.trackLinks || "none", + + // TODO: handle other attributes + }, function(err, response) { + handleResponse(err, response, done); + }); + + }); + + function handleResponse(err, response, done) { + if (err){ + errorMessage(err); + done(); + } else { + var serverId = response.ID; + successMessage(response); + done(response); + } + } + + function errorMessage(err) { + grunt.log.warn('Error creating server: ' + JSON.stringify(err)); + } + + function successMessage(response) { + grunt.log.writeln('Server created: ' + JSON.stringify(response)); + } + +}; From 0873473bf10a8548f00f9815487380a48a8b1d14 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 16 Dec 2016 11:10:24 -0500 Subject: [PATCH 08/32] make create server task idempotent if server with the specified name exists, its config is returned in exactly the same form as a create response. --- tasks/grunt-postmark-servers.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tasks/grunt-postmark-servers.js b/tasks/grunt-postmark-servers.js index c493945..8a718d3 100644 --- a/tasks/grunt-postmark-servers.js +++ b/tasks/grunt-postmark-servers.js @@ -27,8 +27,10 @@ module.exports = function(grunt) { grunt.fail.warn('Missing required server property "name" \n'); } + var name = _data.name || options.name; + client.createServer({ - name: _data.name || options.name, + name: name, color: _data.color || options.color || "Turquoise", smtpApiActivated: _data.smtpApiActivated || options.smtpApiActivated || true, trackOpens: _data.trackOpens || options.trackOpens || false, @@ -36,11 +38,32 @@ module.exports = function(grunt) { // TODO: handle other attributes }, function(err, response) { - handleResponse(err, response, done); + // NOTE if a server with the specified name already exists, we get this response: + // {"status":422,"message":"This server name already exists.","code":603} + if (err && err.status == 422){ + existingServer(client, name, done); + } else { + handleResponse(err, response, done); + } }); }); + // find the server with matching name and return its configuration + function existingServer(client, name, done) { + // listServers implements find-by-property + client.listServers({name: name}, function(err, servers){ + if (err){ + grunt.log.warn('Error retrieving existing server: ' + JSON.stringify(err)); + done(); + } else { + var server = servers.Servers[0]; + grunt.log.writeln('Server found: ' + JSON.stringify(server)); + done(server); + } + }); + } + function handleResponse(err, response, done) { if (err){ errorMessage(err); @@ -52,6 +75,7 @@ module.exports = function(grunt) { } } + function errorMessage(err) { grunt.log.warn('Error creating server: ' + JSON.stringify(err)); } From bcef3169d554c1bacacf0680bfaf37611f6e6708 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 16 Dec 2016 12:58:17 -0500 Subject: [PATCH 09/32] refactor postmark-servers task using more idiomatic grunt usage --- Gruntfile.js | 20 +++++++-------- tasks/grunt-postmark-servers.js | 45 +++++++++++++++++---------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 3a91364..ecb968a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,36 +28,36 @@ module.exports = function(grunt) { } }, - "postmark-templates": { + 'postmark-templates': { options: { serverToken: '<%= secrets.serverToken %>', }, build: { - name: "testing-template-node-js-" + new Date().valueOf(), - subject: "Testing grunt-postmark-templates", + name: 'testing-template-node-js-' + new Date().valueOf(), + subject: 'Testing grunt-postmark-templates', // NOTE these are read from filesystem. globbing not supported htmlFile: 'test/email.html', textFile: 'test/email.txt', } }, - "postmark-servers": { + 'postmark-servers': { options: { - accountToken: '<%= secrets.accountToken %>', - }, - build: { - name: "testing-server-" + new Date().valueOf(), + name: 'testing-server-' + new Date().valueOf(), smtpApiActivated: true, // NOTE complete list of server attributes: // http://developer.postmarkapp.com/developer-api-servers.html#create-server - } + }, }, }); grunt.loadTasks('tasks'); - grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers']); + // test create of an existing server (by name), + grunt.registerTask('duplicate-server', ['postmark-servers', 'postmark-servers']); + + grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers', 'duplicate-server']); }; diff --git a/tasks/grunt-postmark-servers.js b/tasks/grunt-postmark-servers.js index 8a718d3..fe8aa17 100644 --- a/tasks/grunt-postmark-servers.js +++ b/tasks/grunt-postmark-servers.js @@ -7,41 +7,42 @@ module.exports = function(grunt) { 'use strict'; - grunt.registerMultiTask('postmark-servers', 'Create server', function() { + grunt.registerTask('postmark-servers', 'Create server', function() { var done = this.async(); - var options = this.options(); - var _data = this.data; + // default options (attempting to disable all hooks) + var options = this.options({ + color: "turquoise", + smtpApiActivated: true, + trackOpens: false, + trackLinks: 'none', + DeliveryHookUrl: '', + InboundHookUrl: '', + BounceHookUrl: '', + OpenHookUrl: '', + }); + + var accountToken = grunt.config('secrets.accountToken') || options.accountToken; // Check for account token - if (!options.accountToken && !_data.accountToken) { - grunt.fail.warn('Missing option "accountToken" \n'); + if (!accountToken) { + grunt.fail.warn('Missing required config property "accountToken" \n'); } - // Postmark lib - var postmark = require('postmark'); - var client = new postmark.AdminClient(options.accountToken || _data.accountToken); - - // Check for server name - if (!_data.name && !options.name) { + if (!options.name) { grunt.fail.warn('Missing required server property "name" \n'); } - var name = _data.name || options.name; - - client.createServer({ - name: name, - color: _data.color || options.color || "Turquoise", - smtpApiActivated: _data.smtpApiActivated || options.smtpApiActivated || true, - trackOpens: _data.trackOpens || options.trackOpens || false, - trackLinks: _data.trackLinks || options.trackLinks || "none", + // Postmark lib + var postmark = require('postmark'); + var client = new postmark.AdminClient(accountToken); - // TODO: handle other attributes - }, function(err, response) { + // TODO use merge for default options + client.createServer(options, function(err, response) { // NOTE if a server with the specified name already exists, we get this response: // {"status":422,"message":"This server name already exists.","code":603} if (err && err.status == 422){ - existingServer(client, name, done); + existingServer(client, options.name, done); } else { handleResponse(err, response, done); } From 71ccf9116fea49afcdcedc6ad59a107d39d9c2dc Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 16 Dec 2016 12:59:59 -0500 Subject: [PATCH 10/32] duplicate test requires only one additional iteration, not two --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index ecb968a..db942c7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -56,7 +56,7 @@ module.exports = function(grunt) { grunt.loadTasks('tasks'); // test create of an existing server (by name), - grunt.registerTask('duplicate-server', ['postmark-servers', 'postmark-servers']); + grunt.registerTask('duplicate-server', ['postmark-servers']); grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers', 'duplicate-server']); From d3331ca1952338160fd2527f549e5688c84f0750 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 11:37:46 -0500 Subject: [PATCH 11/32] collect serverToken from secrets.json --- Gruntfile.js | 2 +- tasks/grunt-postmark-templates.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index db942c7..0a91249 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,7 +6,7 @@ module.exports = function(grunt) { grunt.initConfig({ - secrets: grunt.file.readJSON('secrets.json'), + secrets: grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}, /* Postmark ------------------------------------------------- */ diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index a35b8cc..7ab3d49 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -12,9 +12,10 @@ module.exports = function(grunt) { var done = this.async(); var options = this.options(); var _data = this.data; + var serverToken = options.serverToken || _data.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); // Check for server token - if (!options.serverToken && !_data.serverToken) { + if (!serverToken) { grunt.fail.warn('Missing required option "serverToken" \n'); } @@ -26,7 +27,7 @@ module.exports = function(grunt) { // Postmark lib var postmark = require('postmark'); - var client = new postmark.Client(options.serverToken || _data.serverToken); + var client = new postmark.Client(serverToken); var htmlBody = _data.htmlBody || options.htmlBody; var textBody = _data.textBody || options.textBody; From a48658c1d82bee8b4481ec0e58bf1fc702083b42 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 11:38:37 -0500 Subject: [PATCH 12/32] read template config from templates.json --- Gruntfile.js | 40 +++++++++++++++++++++---------- tasks/grunt-postmark-templates.js | 36 ++++++++++++++-------------- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0a91249..cadae43 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,6 +7,7 @@ module.exports = function(grunt) { grunt.initConfig({ secrets: grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}, + templates: grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null, /* Postmark ------------------------------------------------- */ @@ -28,19 +29,6 @@ module.exports = function(grunt) { } }, - 'postmark-templates': { - options: { - serverToken: '<%= secrets.serverToken %>', - }, - build: { - name: 'testing-template-node-js-' + new Date().valueOf(), - subject: 'Testing grunt-postmark-templates', - // NOTE these are read from filesystem. globbing not supported - htmlFile: 'test/email.html', - textFile: 'test/email.txt', - } - }, - 'postmark-servers': { options: { name: 'testing-server-' + new Date().valueOf(), @@ -51,6 +39,27 @@ module.exports = function(grunt) { }, }, + 'postmark-templates': { + build: { + name: 'testing-template-node-js-' + new Date().valueOf(), + subject: 'Testing grunt-postmark-templates', + // NOTE these are read from filesystem. globbing not supported + htmlFile: 'test/confirm_email.html', + textFile: 'test/confirm_email.txt', + } + }, + + 'update-templates': { + 'templates': { + // key is used as template name + test_email: { + subject: 'Testing grunt postmark-update-templates task', + htmlFile: 'test/confirm_email.html', + textFile: 'test/confirm_email.txt', + } + } + } + }); grunt.loadTasks('tasks'); @@ -58,6 +67,11 @@ module.exports = function(grunt) { // test create of an existing server (by name), grunt.registerTask('duplicate-server', ['postmark-servers']); + grunt.registerTask('update-templates', 'create or update a set of templates', function() { + grunt.config.set('postmark-templates', grunt.config('templates') || grunt.config('update-templates.templates')); + grunt.task.run('postmark-templates'); + }); + grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers', 'duplicate-server']); }; diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 7ab3d49..81ff321 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -7,11 +7,13 @@ module.exports = function(grunt) { 'use strict'; - grunt.registerMultiTask('postmark-templates', 'Push templates', function() { + grunt.registerMultiTask('postmark-templates', 'Create or update PostMark template', function() { var done = this.async(); var options = this.options(); var _data = this.data; + var tmplName = _data.name || this.target; + var serverToken = options.serverToken || _data.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); // Check for server token @@ -19,11 +21,13 @@ module.exports = function(grunt) { grunt.fail.warn('Missing required option "serverToken" \n'); } - // Check for required attributes - // TODO: require one of fooBody or fooFile - ['name', 'subject'].forEach(function(name){ - requireProperty(name, options, _data); - }); + if (!tmplName) { + grunt.fail.warn('Missing required property "name" \n'); + } + + if (!_data.subject) { + grunt.fail.warn('Missing required property "subject" \n'); + } // Postmark lib var postmark = require('postmark'); @@ -38,30 +42,25 @@ module.exports = function(grunt) { textBody = grunt.file.read(_data.textFile); } client.createTemplate({ - name: _data.name || options.name, + name: tmplName, textBody: textBody, htmlBody: htmlBody, - subject: _data.subject || options.subject, + subject: _data.subject, }, function(err, response) { handleResponse(err, response, done); }); }); - function requireProperty(name, options, data) { - if (!data[name] && !options[name]) { - grunt.fail.warn('Missing required property "' + name + '" \n'); - } - } - function handleResponse(err, response, done) { if (err){ errorMessage(err); done(); } else { var templateId = response.TemplateId; - successMessage(templateId); - done(templateId); + var name = response.Name; + successMessage(name, templateId); + done({name: name, templateId: templateId}); } } @@ -69,8 +68,9 @@ module.exports = function(grunt) { grunt.log.warn('Error response: ' + JSON.stringify(response)); } - function successMessage(templateId) { - grunt.log.writeln('Template pushed: ' + JSON.stringify(templateId)); + function successMessage(name, templateId) { + grunt.log.writeln('Template ' + name + ' pushed: ' + JSON.stringify(templateId)); + } }; From a650a90cf0fe4c1d68d51c326d50c62222f9e802 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 11:44:37 -0500 Subject: [PATCH 13/32] clean up example template files --- Gruntfile.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index cadae43..990653f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -44,18 +44,19 @@ module.exports = function(grunt) { name: 'testing-template-node-js-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', // NOTE these are read from filesystem. globbing not supported - htmlFile: 'test/confirm_email.html', - textFile: 'test/confirm_email.txt', + htmlFile: 'test/email.html', + textFile: 'test/email.txt', } }, + // you can either specify the template configuration here, or in templates.json 'update-templates': { 'templates': { // key is used as template name test_email: { - subject: 'Testing grunt postmark-update-templates task', - htmlFile: 'test/confirm_email.html', - textFile: 'test/confirm_email.txt', + subject: 'Testing grunt postmark-update-templates task', + htmlFile: 'test/email.html', + textFile: 'test/email.txt', } } } From d87365d8b7104d65c360dc31776f7987576bc43d Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 14:29:54 -0500 Subject: [PATCH 14/32] simplify configuration --- Gruntfile.js | 22 +++------------------- tasks/grunt-postmark-templates.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 990653f..d2c18db 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -39,26 +39,15 @@ module.exports = function(grunt) { }, }, + // you can either specify the template configuration here, or in templates.json 'postmark-templates': { - build: { - name: 'testing-template-node-js-' + new Date().valueOf(), + test_email_file: { + name: 'testing-postmark-templates-js-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', // NOTE these are read from filesystem. globbing not supported htmlFile: 'test/email.html', textFile: 'test/email.txt', } - }, - - // you can either specify the template configuration here, or in templates.json - 'update-templates': { - 'templates': { - // key is used as template name - test_email: { - subject: 'Testing grunt postmark-update-templates task', - htmlFile: 'test/email.html', - textFile: 'test/email.txt', - } - } } }); @@ -68,11 +57,6 @@ module.exports = function(grunt) { // test create of an existing server (by name), grunt.registerTask('duplicate-server', ['postmark-servers']); - grunt.registerTask('update-templates', 'create or update a set of templates', function() { - grunt.config.set('postmark-templates', grunt.config('templates') || grunt.config('update-templates.templates')); - grunt.task.run('postmark-templates'); - }); - grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers', 'duplicate-server']); }; diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 81ff321..1d4865e 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -7,7 +7,12 @@ module.exports = function(grunt) { 'use strict'; - grunt.registerMultiTask('postmark-templates', 'Create or update PostMark template', function() { + grunt.registerTask('postmark-templates', 'create or update a set of templates', function() { + grunt.config.set('_postmark-template', grunt.config('templates') || grunt.config('postmark-templates')); + grunt.task.run('_postmark-template'); + }); + + grunt.registerMultiTask('_postmark-template', 'Create or update PostMark template', function() { var done = this.async(); var options = this.options(); @@ -59,8 +64,10 @@ module.exports = function(grunt) { } else { var templateId = response.TemplateId; var name = response.Name; + var result = {name: name, templateId: templateId}; + grunt.config.merge('updatedTemplates', result); successMessage(name, templateId); - done({name: name, templateId: templateId}); + done(result); } } From bb51efc9831364269e0e08ec0252098675ba415b Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 16:01:39 -0500 Subject: [PATCH 15/32] use more canonical htmlBody and textBody --- Gruntfile.js | 4 ++-- tasks/grunt-postmark-templates.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index d2c18db..bf9236a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -45,8 +45,8 @@ module.exports = function(grunt) { name: 'testing-postmark-templates-js-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', // NOTE these are read from filesystem. globbing not supported - htmlFile: 'test/email.html', - textFile: 'test/email.txt', + htmlBody: 'test/email.html', + textBody: 'test/email.txt', } } diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 1d4865e..665c713 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -40,11 +40,11 @@ module.exports = function(grunt) { var htmlBody = _data.htmlBody || options.htmlBody; var textBody = _data.textBody || options.textBody; - if (_data.htmlFile) { - htmlBody = grunt.file.read(_data.htmlFile); + if (_data.htmlBody) { + htmlBody = grunt.file.read(htmlBody); } - if (_data.textFile) { - textBody = grunt.file.read(_data.textFile); + if (_data.textBody) { + textBody = grunt.file.read(textBody); } client.createTemplate({ name: tmplName, From 3967d29d4002b42b6a6b9caf9a59f7067c1dc049 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 23 Dec 2016 16:21:38 -0500 Subject: [PATCH 16/32] read templates.json in the postmark-templates task --- tasks/grunt-postmark-templates.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 665c713..c0965e3 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -8,7 +8,10 @@ module.exports = function(grunt) { 'use strict'; grunt.registerTask('postmark-templates', 'create or update a set of templates', function() { - grunt.config.set('_postmark-template', grunt.config('templates') || grunt.config('postmark-templates')); + var templates = grunt.config('templates') || grunt.config('postmark-templates'); + templates = templates || grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null; + + grunt.config.set('_postmark-template', templates); grunt.task.run('_postmark-template'); }); From 82fb13c92abcd2a6b5a6a962514a1988933b33bb Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 17:32:56 -0500 Subject: [PATCH 17/32] checkpoint: making progress toward saving templateID output --- tasks/grunt-postmark-templates.js | 69 +++++++++++++++++++------------ 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index c0965e3..afa2399 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -9,66 +9,69 @@ module.exports = function(grunt) { grunt.registerTask('postmark-templates', 'create or update a set of templates', function() { var templates = grunt.config('templates') || grunt.config('postmark-templates'); - templates = templates || grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null; + templates = templates || (grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null); grunt.config.set('_postmark-template', templates); + grunt.config.set('updatedTemplates', []); grunt.task.run('_postmark-template'); + grunt.task.run('_postmark-output'); }); grunt.registerMultiTask('_postmark-template', 'Create or update PostMark template', function() { var done = this.async(); var options = this.options(); - var _data = this.data; - var tmplName = _data.name || this.target; + var template = this.data; - var serverToken = options.serverToken || _data.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); + var serverToken = options.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); // Check for server token if (!serverToken) { grunt.fail.warn('Missing required option "serverToken" \n'); } - if (!tmplName) { - grunt.fail.warn('Missing required property "name" \n'); + template.name = template.name || this.target; + if (!template.name) { + grunt.fail.warn('Missing required template property "name" \n'); } - if (!_data.subject) { - grunt.fail.warn('Missing required property "subject" \n'); + if (!template.subject) { + grunt.fail.warn('Missing required template property "subject" \n'); } + grunt.log.writeln('tmpl options: ' + JSON.stringify(options)); + grunt.log.writeln('template: ' + JSON.stringify(template)); + // Postmark lib var postmark = require('postmark'); var client = new postmark.Client(serverToken); - var htmlBody = _data.htmlBody || options.htmlBody; - var textBody = _data.textBody || options.textBody; - if (_data.htmlBody) { - htmlBody = grunt.file.read(htmlBody); + var expanded = Object.assign({}, template); + + if (expanded.htmlBody) { + expanded.htmlBody = grunt.file.read(expanded.htmlBody); } - if (_data.textBody) { - textBody = grunt.file.read(textBody); + if (expanded.textBody) { + expanded.textBody = grunt.file.read(expanded.textBody); } - client.createTemplate({ - name: tmplName, - textBody: textBody, - htmlBody: htmlBody, - subject: _data.subject, - }, function(err, response) { - handleResponse(err, response, done); + + client.createTemplate(expanded, function(err, response) { + handleResponse(template, err, response, done); }); }); - function handleResponse(err, response, done) { + function handleResponse(template, err, response, done) { if (err){ errorMessage(err); done(); } else { - var templateId = response.TemplateId; - var name = response.Name; - var result = {name: name, templateId: templateId}; - grunt.config.merge('updatedTemplates', result); + template.templateId = response.TemplateId; + // append this record to the result array + var upd = grunt.config.get('updatedTemplates'); + upd.unshift(template); + grunt.config.set('updatedTemplates', upd); + successMessage(name, templateId); done(result); } @@ -83,4 +86,18 @@ module.exports = function(grunt) { } + grunt.registerTask('_postmark-output', 'writes out the resulting template IDs', function() { + + var options = this.options({ + filename: "templates-output.json" + }); + + var results = grunt.config('updatedTemplates'); + + grunt.file.write(options.filename, JSON.stringify(results, null, 1)); + + grunt.log.writeln("Updated template information written to " + options.filename); + + }); + }; From a0a6f6dd3b43ae2be4c0e4f0d0600759947269cb Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:04:29 -0500 Subject: [PATCH 18/32] output template info (including IDs) in templates.json format --- tasks/grunt-postmark-templates.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index afa2399..c747a8e 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -12,7 +12,7 @@ module.exports = function(grunt) { templates = templates || (grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null); grunt.config.set('_postmark-template', templates); - grunt.config.set('updatedTemplates', []); + grunt.config.set('updatedTemplates', {}); grunt.task.run('_postmark-template'); grunt.task.run('_postmark-output'); }); @@ -31,6 +31,7 @@ module.exports = function(grunt) { } template.name = template.name || this.target; + if (!template.name) { grunt.fail.warn('Missing required template property "name" \n'); } @@ -39,13 +40,10 @@ module.exports = function(grunt) { grunt.fail.warn('Missing required template property "subject" \n'); } - grunt.log.writeln('tmpl options: ' + JSON.stringify(options)); - grunt.log.writeln('template: ' + JSON.stringify(template)); - - // Postmark lib var postmark = require('postmark'); var client = new postmark.Client(serverToken); + // read the referenced files, but hold on to the original filenames var expanded = Object.assign({}, template); if (expanded.htmlBody) { @@ -69,21 +67,26 @@ module.exports = function(grunt) { template.templateId = response.TemplateId; // append this record to the result array var upd = grunt.config.get('updatedTemplates'); - upd.unshift(template); + var tname = template.name; + delete template.name; + upd[tname] = template; grunt.config.set('updatedTemplates', upd); - successMessage(name, templateId); - done(result); + successMessage(tname, template.templateId); + done(template); } } - function errorMessage(response) { - grunt.log.warn('Error response: ' + JSON.stringify(response)); + function errorMessage(err) { + if (err.message) { + grunt.log.warn('Error: ' + err.message); + } else { + grunt.log.warn('Error: ' + JSON.stringify(err)); + } } function successMessage(name, templateId) { grunt.log.writeln('Template ' + name + ' pushed: ' + JSON.stringify(templateId)); - } grunt.registerTask('_postmark-output', 'writes out the resulting template IDs', function() { @@ -94,7 +97,7 @@ module.exports = function(grunt) { var results = grunt.config('updatedTemplates'); - grunt.file.write(options.filename, JSON.stringify(results, null, 1)); + grunt.file.write(options.filename, JSON.stringify(results, null, 2)); grunt.log.writeln("Updated template information written to " + options.filename); From 8fb62e577a8703f1c0349007882483ee79687308 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:05:00 -0500 Subject: [PATCH 19/32] update existing templates if templateID is present --- tasks/grunt-postmark-templates.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index c747a8e..c6f3306 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -52,10 +52,16 @@ module.exports = function(grunt) { if (expanded.textBody) { expanded.textBody = grunt.file.read(expanded.textBody); } - - client.createTemplate(expanded, function(err, response) { - handleResponse(template, err, response, done); - }); + + if (template.templateId) { + client.editTemplate(template.templateId, expanded, function(err, response) { + handleResponse(template, err, response, done); + }); + } else { + client.createTemplate(expanded, function(err, response) { + handleResponse(template, err, response, done); + }); + } }); From a518367f6ef43e5f1fc567d2662f4a58b1853fb0 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:29:37 -0500 Subject: [PATCH 20/32] if update fails with bad Id, then revert to create --- .gitignore | 3 ++- tasks/grunt-postmark-templates.js | 36 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index add4ce7..7e27fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .idea -secrets.json \ No newline at end of file +secrets.json +templates-output.json diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index c6f3306..fe9db11 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -7,6 +7,30 @@ module.exports = function(grunt) { 'use strict'; + // There are a number of different ways to populate this configuration object, + // including gruntfile configuration for "postmark-templates", + // or a templates.json file in your project. + // + // The object should have the following format, using the template name as the key. + // Here is an example templates.json file from a Mailmason project: + // + // { + // "confirm_email": { + // "subject": "Please confirm your email", + // "htmlBody": "dist/confirm_email.html", + // "textBody": "dist/confirm_email.txt", + // "templateId": 1201660 + // } + // } + // + // templateId is optional; if present, the template with the given ID will be updated. This will prevent + // proliferation of outdated versions of templates with the same name, and depending on how you're using + // them, reduce the configuration overhead (for example publishing these templateIds to your backend). + // + // The postmark-templates task will write an output file in this format including the current templateId. + // The default output file is named templates-output.json. You may ignore this file, or manually copy it + // to templates.json after a successful run. + grunt.registerTask('postmark-templates', 'create or update a set of templates', function() { var templates = grunt.config('templates') || grunt.config('postmark-templates'); templates = templates || (grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null); @@ -25,7 +49,6 @@ module.exports = function(grunt) { var serverToken = options.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); - // Check for server token if (!serverToken) { grunt.fail.warn('Missing required option "serverToken" \n'); } @@ -55,7 +78,16 @@ module.exports = function(grunt) { if (template.templateId) { client.editTemplate(template.templateId, expanded, function(err, response) { - handleResponse(template, err, response, done); + if (err && err.code === 1101) { + grunt.log.warn('Template ' + template.templateId + ' not found, so attempting create'); + delete template.templateId; + delete expanded.templateId; + client.createTemplate(expanded, function(err, response) { + handleResponse(template, err, response, done); + }); + } else { + handleResponse(template, err, response, done); + } }); } else { client.createTemplate(expanded, function(err, response) { From 61311967f5ab73a73a59537b0ce84e08c8acebe1 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:31:48 -0500 Subject: [PATCH 21/32] move postmark-servers task to separate PR --- Gruntfile.js | 10 ---- tasks/grunt-postmark-servers.js | 88 --------------------------------- 2 files changed, 98 deletions(-) delete mode 100644 tasks/grunt-postmark-servers.js diff --git a/Gruntfile.js b/Gruntfile.js index bf9236a..786a2e8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -29,16 +29,6 @@ module.exports = function(grunt) { } }, - 'postmark-servers': { - options: { - name: 'testing-server-' + new Date().valueOf(), - smtpApiActivated: true, - - // NOTE complete list of server attributes: - // http://developer.postmarkapp.com/developer-api-servers.html#create-server - }, - }, - // you can either specify the template configuration here, or in templates.json 'postmark-templates': { test_email_file: { diff --git a/tasks/grunt-postmark-servers.js b/tasks/grunt-postmark-servers.js deleted file mode 100644 index fe8aa17..0000000 --- a/tasks/grunt-postmark-servers.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * grunt-postmark-servers - * https://github.com/wildbit/grunt-postmark.git - */ - -module.exports = function(grunt) { - - 'use strict'; - - grunt.registerTask('postmark-servers', 'Create server', function() { - - var done = this.async(); - - // default options (attempting to disable all hooks) - var options = this.options({ - color: "turquoise", - smtpApiActivated: true, - trackOpens: false, - trackLinks: 'none', - DeliveryHookUrl: '', - InboundHookUrl: '', - BounceHookUrl: '', - OpenHookUrl: '', - }); - - var accountToken = grunt.config('secrets.accountToken') || options.accountToken; - // Check for account token - if (!accountToken) { - grunt.fail.warn('Missing required config property "accountToken" \n'); - } - - if (!options.name) { - grunt.fail.warn('Missing required server property "name" \n'); - } - - // Postmark lib - var postmark = require('postmark'); - var client = new postmark.AdminClient(accountToken); - - // TODO use merge for default options - client.createServer(options, function(err, response) { - // NOTE if a server with the specified name already exists, we get this response: - // {"status":422,"message":"This server name already exists.","code":603} - if (err && err.status == 422){ - existingServer(client, options.name, done); - } else { - handleResponse(err, response, done); - } - }); - - }); - - // find the server with matching name and return its configuration - function existingServer(client, name, done) { - // listServers implements find-by-property - client.listServers({name: name}, function(err, servers){ - if (err){ - grunt.log.warn('Error retrieving existing server: ' + JSON.stringify(err)); - done(); - } else { - var server = servers.Servers[0]; - grunt.log.writeln('Server found: ' + JSON.stringify(server)); - done(server); - } - }); - } - - function handleResponse(err, response, done) { - if (err){ - errorMessage(err); - done(); - } else { - var serverId = response.ID; - successMessage(response); - done(response); - } - } - - - function errorMessage(err) { - grunt.log.warn('Error creating server: ' + JSON.stringify(err)); - } - - function successMessage(response) { - grunt.log.writeln('Server created: ' + JSON.stringify(response)); - } - -}; From ac17e378e3ce800a6d0474c89f416c0079061536 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:35:07 -0500 Subject: [PATCH 22/32] remove references to postmark-servers (for another PR) --- Gruntfile.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 786a2e8..b4c307f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -44,9 +44,6 @@ module.exports = function(grunt) { grunt.loadTasks('tasks'); - // test create of an existing server (by name), - grunt.registerTask('duplicate-server', ['postmark-servers']); - - grunt.registerTask('default', ['postmark', 'postmark-templates', 'postmark-servers', 'duplicate-server']); + grunt.registerTask('default', ['postmark', 'postmark-templates']); }; From f3a7f7e2e57055be35f24a1d83899d07645fe22c Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Tue, 3 Jan 2017 21:37:54 -0500 Subject: [PATCH 23/32] one last comment --- tasks/grunt-postmark-templates.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index fe9db11..5f5123c 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -1,5 +1,7 @@ /* * grunt-postmark-templates + * push templates to a Postmark server for use with SendTemplatedEmail + * * https://github.com/wildbit/grunt-postmark.git */ From 4a0b60cda639899ae5eed9b36cddd48e73bc8720 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 13 Jan 2017 15:04:25 -0500 Subject: [PATCH 24/32] node.js style callback --- tasks/grunt-postmark-templates.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 5f5123c..2cfdf88 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -85,21 +85,21 @@ module.exports = function(grunt) { delete template.templateId; delete expanded.templateId; client.createTemplate(expanded, function(err, response) { - handleResponse(template, err, response, done); + handleResponse(err, response, done, template); }); } else { - handleResponse(template, err, response, done); + handleResponse(err, response, done, template); } }); } else { client.createTemplate(expanded, function(err, response) { - handleResponse(template, err, response, done); + handleResponse(err, response, done, template); }); } }); - function handleResponse(template, err, response, done) { + function handleResponse(err, response, done, template) { if (err){ errorMessage(err); done(); From aa3194b574784e9f1ea04e90210ed74df2731e63 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 13 Jan 2017 15:38:42 -0500 Subject: [PATCH 25/32] simplify configuration following suggestions from @derekrushforth --- Gruntfile.js | 15 ++++++++--- tasks/grunt-postmark-templates.js | 43 +++++-------------------------- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b4c307f..746d869 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,7 +7,6 @@ module.exports = function(grunt) { grunt.initConfig({ secrets: grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}, - templates: grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null, /* Postmark ------------------------------------------------- */ @@ -31,8 +30,15 @@ module.exports = function(grunt) { // you can either specify the template configuration here, or in templates.json 'postmark-templates': { - test_email_file: { - name: 'testing-postmark-templates-js-' + new Date().valueOf(), + test_email: { + name: 'testing-postmark-templates-js1-' + new Date().valueOf(), + subject: 'Testing grunt-postmark-templates', + // NOTE these are read from filesystem. globbing not supported + htmlBody: 'test/email.html', + textBody: 'test/email.txt', + }, + test_email_again: { + name: 'testing-postmark-templates-js2-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', // NOTE these are read from filesystem. globbing not supported htmlBody: 'test/email.html', @@ -44,6 +50,9 @@ module.exports = function(grunt) { grunt.loadTasks('tasks'); + // you can also get a JSON report of uploaded templates (default filename: templates-output.json) + grunt.registerTask('all-templates', ['postmark-templates', 'postmark-templates-output']); + grunt.registerTask('default', ['postmark', 'postmark-templates']); }; diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 2cfdf88..c575b72 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -9,41 +9,7 @@ module.exports = function(grunt) { 'use strict'; - // There are a number of different ways to populate this configuration object, - // including gruntfile configuration for "postmark-templates", - // or a templates.json file in your project. - // - // The object should have the following format, using the template name as the key. - // Here is an example templates.json file from a Mailmason project: - // - // { - // "confirm_email": { - // "subject": "Please confirm your email", - // "htmlBody": "dist/confirm_email.html", - // "textBody": "dist/confirm_email.txt", - // "templateId": 1201660 - // } - // } - // - // templateId is optional; if present, the template with the given ID will be updated. This will prevent - // proliferation of outdated versions of templates with the same name, and depending on how you're using - // them, reduce the configuration overhead (for example publishing these templateIds to your backend). - // - // The postmark-templates task will write an output file in this format including the current templateId. - // The default output file is named templates-output.json. You may ignore this file, or manually copy it - // to templates.json after a successful run. - - grunt.registerTask('postmark-templates', 'create or update a set of templates', function() { - var templates = grunt.config('templates') || grunt.config('postmark-templates'); - templates = templates || (grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : null); - - grunt.config.set('_postmark-template', templates); - grunt.config.set('updatedTemplates', {}); - grunt.task.run('_postmark-template'); - grunt.task.run('_postmark-output'); - }); - - grunt.registerMultiTask('_postmark-template', 'Create or update PostMark template', function() { + grunt.registerMultiTask('postmark-templates', 'Create or update Postmark templates', function() { var done = this.async(); var options = this.options(); @@ -106,7 +72,7 @@ module.exports = function(grunt) { } else { template.templateId = response.TemplateId; // append this record to the result array - var upd = grunt.config.get('updatedTemplates'); + var upd = grunt.config.get('updatedTemplates') || {}; var tname = template.name; delete template.name; upd[tname] = template; @@ -129,7 +95,10 @@ module.exports = function(grunt) { grunt.log.writeln('Template ' + name + ' pushed: ' + JSON.stringify(templateId)); } - grunt.registerTask('_postmark-output', 'writes out the resulting template IDs', function() { + // invoke this task after postmark-templates to get an output file containing the resulting template IDs + // this is in the same format as the postmark-templates config. + + grunt.registerTask('postmark-templates-output', 'writes out the resulting template IDs', function() { var options = this.options({ filename: "templates-output.json" From 90ec8054e476815956996fbd42ed31849d50c6b4 Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 13 Jan 2017 15:51:13 -0500 Subject: [PATCH 26/32] log "created" vs "updated" --- tasks/grunt-postmark-templates.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index c575b72..1778e7e 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -52,13 +52,16 @@ module.exports = function(grunt) { delete expanded.templateId; client.createTemplate(expanded, function(err, response) { handleResponse(err, response, done, template); + grunt.log.writeln('Template ' + template.name + ' created: ' + JSON.stringify(response.TemplateId)); }); } else { + grunt.log.writeln('Template ' + template.name + ' updated: ' + JSON.stringify(response.TemplateId)); handleResponse(err, response, done, template); } }); } else { client.createTemplate(expanded, function(err, response) { + grunt.log.writeln('Template ' + template.name + ' created: ' + JSON.stringify(response.TemplateId)); handleResponse(err, response, done, template); }); } @@ -71,14 +74,13 @@ module.exports = function(grunt) { done(); } else { template.templateId = response.TemplateId; - // append this record to the result array + // append this record to the result array, used by postmark-templates-output task var upd = grunt.config.get('updatedTemplates') || {}; var tname = template.name; delete template.name; upd[tname] = template; grunt.config.set('updatedTemplates', upd); - successMessage(tname, template.templateId); done(template); } } @@ -91,10 +93,6 @@ module.exports = function(grunt) { } } - function successMessage(name, templateId) { - grunt.log.writeln('Template ' + name + ' pushed: ' + JSON.stringify(templateId)); - } - // invoke this task after postmark-templates to get an output file containing the resulting template IDs // this is in the same format as the postmark-templates config. From 315fd1930b4fcf934fffc4851b1627db09d01c7c Mon Sep 17 00:00:00 2001 From: Brian Del Vecchio Date: Fri, 13 Jan 2017 15:51:46 -0500 Subject: [PATCH 27/32] sample Gruntfile reads templates.json or defaults to inline config --- Gruntfile.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 746d869..45a90d0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -29,18 +29,16 @@ module.exports = function(grunt) { }, // you can either specify the template configuration here, or in templates.json - 'postmark-templates': { + 'postmark-templates': grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : { test_email: { name: 'testing-postmark-templates-js1-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', - // NOTE these are read from filesystem. globbing not supported htmlBody: 'test/email.html', textBody: 'test/email.txt', }, test_email_again: { name: 'testing-postmark-templates-js2-' + new Date().valueOf(), subject: 'Testing grunt-postmark-templates', - // NOTE these are read from filesystem. globbing not supported htmlBody: 'test/email.html', textBody: 'test/email.txt', } @@ -51,7 +49,7 @@ module.exports = function(grunt) { grunt.loadTasks('tasks'); // you can also get a JSON report of uploaded templates (default filename: templates-output.json) - grunt.registerTask('all-templates', ['postmark-templates', 'postmark-templates-output']); + grunt.registerTask('templates-logged', ['postmark-templates', 'postmark-templates-output']); grunt.registerTask('default', ['postmark', 'postmark-templates']); From 19fc6c477feb0b85e77a76239efdacea602be32e Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Fri, 7 Jul 2017 22:05:53 -0700 Subject: [PATCH 28/32] Add the ability for `mailmason` to generate and upload/update templates. --- .gitignore | 2 +- Gruntfile.js | 58 ++++++++++-------- README.md | 2 + example_config.json | 17 ++++++ example_secrets.json | 12 ++++ secrets_example.json | 5 -- tasks/grunt-postmark-templates.js | 99 ++++++++++++++++--------------- tasks/grunt-postmark.js | 8 ++- 8 files changed, 124 insertions(+), 79 deletions(-) create mode 100644 example_config.json create mode 100644 example_secrets.json delete mode 100644 secrets_example.json diff --git a/.gitignore b/.gitignore index 7e27fa6..d00798d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules .idea +config.json secrets.json -templates-output.json diff --git a/Gruntfile.js b/Gruntfile.js index 45a90d0..9a10fc6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,54 +3,62 @@ * https://github.com/wildbit/grunt-postmark.git */ -module.exports = function(grunt) { +module.exports = function (grunt) { + var secret = grunt.file.readJSON('secrets.json'); + var config = grunt.file.readJSON('config.json'); grunt.initConfig({ - secrets: grunt.file.exists('secrets.json') ? grunt.file.readJSON('secrets.json') : {}, + secret: secret, + config: config, /* Postmark - ------------------------------------------------- */ + ------------------------------------------------- */ postmark: { options: { - serverToken: '<%= secrets.serverToken %>' + serverToken: "<%= secret.postmark.server_token %>", }, email: { - from: '<%= secrets.testSender %>', - to: '<%= secrets.testRecipient %>', - subject: 'Yo', + from: "<%= config.postmark.from %>", + to: "<%= config.postmark.to %>", + subject: "<%= config.postmark.subject %>", src: ['test/email.html'] }, bulk: { - from: '<%= secrets.testSender %>', - to: '<%= secrets.testRecipient %>', - subject: 'Hey', + from: "<%= config.postmark.from %>", + to: "<%= config.postmark.to %>", + subject: "<%= config.postmark.subject %>", src: ['test/*.html'] } }, // you can either specify the template configuration here, or in templates.json - 'postmark-templates': grunt.file.exists('templates.json') ? grunt.file.readJSON('templates.json') : { - test_email: { - name: 'testing-postmark-templates-js1-' + new Date().valueOf(), - subject: 'Testing grunt-postmark-templates', - htmlBody: 'test/email.html', - textBody: 'test/email.txt', + 'postmark-templates-upload': config.templates && config.templates + ? config.templates.output_file || config.templates.file + : { + test_email: { + name: 'testing-postmark-templates-js1-' + new Date().valueOf(), + subject: 'Testing grunt-postmark-templates', + htmlBody: 'test/email.html', + textBody: 'test/email.txt', + }, + test_email_again: { + name: 'testing-postmark-templates-js2-' + new Date().valueOf(), + subject: 'Testing grunt-postmark-templates', + htmlBody: 'test/email.html', + textBody: 'test/email.txt', + } }, - test_email_again: { - name: 'testing-postmark-templates-js2-' + new Date().valueOf(), - subject: 'Testing grunt-postmark-templates', - htmlBody: 'test/email.html', - textBody: 'test/email.txt', + + 'postmark-templates-output': { + options: { + outputFile: '<%= config.templates.output_file || config.templates.file %>', + cleanOutput: '<%= config.templates.clean_output %>' } } - }); grunt.loadTasks('tasks'); - // you can also get a JSON report of uploaded templates (default filename: templates-output.json) - grunt.registerTask('templates-logged', ['postmark-templates', 'postmark-templates-output']); - grunt.registerTask('default', ['postmark', 'postmark-templates']); }; diff --git a/README.md b/README.md index 5239406..9b9ce5a 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ After the plugin is installed, it can be enabled in your Gruntfile: grunt.loadNpmTasks('grunt-postmark'); ``` +You'll need to add a [`config.json`](https://github.com/wildbit/mailmason/wiki/Getting-Started#create-configjson-required) and a [`secrets.json`](https://github.com/wildbit/mailmason/wiki/Getting-Started#create-secretsjson-optional) per the `mailmason` configuration. + ## Postmark task _Run this task with the `grunt postmark` command._ diff --git a/example_config.json b/example_config.json new file mode 100644 index 0000000..10cfaf1 --- /dev/null +++ b/example_config.json @@ -0,0 +1,17 @@ +// 1. Copy this file to "config.json" for your own version that won't be +// tracked in source control. +// +// 2. Delete these comments from the configuration so Grunt doesn't get confused +// +// 3. Visit the following page for details: +// https://github.com/wildbit/mailmason/wiki/Getting-Started#configjson +{ + "postmark": { + "from": "jane@example.com", + "to": "jon@example.com", + "subject": "Test Email" + }, + "templates": { + "file": "templates.json" + } +} diff --git a/example_secrets.json b/example_secrets.json new file mode 100644 index 0000000..bede227 --- /dev/null +++ b/example_secrets.json @@ -0,0 +1,12 @@ +// 1. Copy this file to "secrets.json" for your own version that won't be +// tracked in source control. +// +// 2. Delete these comments from the configuration so Grunt doesn't get confused +// +// 3. Visit the following page for details: +// https://github.com/wildbit/mailmason/wiki/Getting-Started#secretsjson +{ + "postmark": { + "server_token": "YOUR_POSTMARK_SERVER_TOKEN_HERE" + } +} diff --git a/secrets_example.json b/secrets_example.json deleted file mode 100644 index 1cc375a..0000000 --- a/secrets_example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "serverToken": "POSTMARK_API_TEST", - "testSender": "you@youremail.com", - "testRecipient": "you@youremail.com" -} diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 1778e7e..721a684 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -5,30 +5,32 @@ * https://github.com/wildbit/grunt-postmark.git */ -module.exports = function(grunt) { - - 'use strict'; - - grunt.registerMultiTask('postmark-templates', 'Create or update Postmark templates', function() { - +module.exports = function (grunt) { + grunt.registerMultiTask('postmark-templates-upload', 'Create or update Postmark templates', function () { var done = this.async(); var options = this.options(); var template = this.data; - var serverToken = options.serverToken || grunt.config('secrets.serverToken') || grunt.config('secret.postmark.server_token'); + var serverToken = options.serverToken || grunt.config('secret.postmark.server_token'); if (!serverToken) { - grunt.fail.warn('Missing required option "serverToken" \n'); + grunt.fail.warn('Missing Postmark server token \n'); + } + + if (!template.Name) { + grunt.fail.warn('Missing required template property "Name" \n'); } - template.name = template.name || this.target; + if (!template.Subject) { + grunt.fail.warn('Missing required template property "Subject" \n'); + } - if (!template.name) { - grunt.fail.warn('Missing required template property "name" \n'); + if (!template.HtmlBody) { + grunt.log.error('Missing template property "HtmlBody" \n'); } - if (!template.subject) { - grunt.fail.warn('Missing required template property "subject" \n'); + if (!template.TextBody) { + grunt.log.error('Missing template property "TextBody" \n'); } var postmark = require('postmark'); @@ -37,31 +39,24 @@ module.exports = function(grunt) { // read the referenced files, but hold on to the original filenames var expanded = Object.assign({}, template); - if (expanded.htmlBody) { - expanded.htmlBody = grunt.file.read(expanded.htmlBody); - } - if (expanded.textBody) { - expanded.textBody = grunt.file.read(expanded.textBody); - } - - if (template.templateId) { - client.editTemplate(template.templateId, expanded, function(err, response) { + if (template.TemplateId) { + client.editTemplate(template.TemplateId, expanded, function (err, response) { if (err && err.code === 1101) { - grunt.log.warn('Template ' + template.templateId + ' not found, so attempting create'); - delete template.templateId; - delete expanded.templateId; - client.createTemplate(expanded, function(err, response) { + grunt.log.warn('Template ' + template.TemplateId + ' not found, so attempting create'); + delete template.TemplateId; + delete expanded.TemplateId; + client.createTemplate(expanded, function (err, response) { + grunt.log.writeln('Template ' + template.Name + ' created: ' + JSON.stringify(response.TemplateId)); handleResponse(err, response, done, template); - grunt.log.writeln('Template ' + template.name + ' created: ' + JSON.stringify(response.TemplateId)); }); } else { - grunt.log.writeln('Template ' + template.name + ' updated: ' + JSON.stringify(response.TemplateId)); + grunt.log.writeln('Template ' + template.Name + ' updated: ' + JSON.stringify(response.TemplateId)); handleResponse(err, response, done, template); } }); } else { - client.createTemplate(expanded, function(err, response) { - grunt.log.writeln('Template ' + template.name + ' created: ' + JSON.stringify(response.TemplateId)); + client.createTemplate(expanded, function (err, response) { + grunt.log.writeln('Template ' + template.Name + ' created: ' + JSON.stringify(response.TemplateId)); handleResponse(err, response, done, template); }); } @@ -73,15 +68,13 @@ module.exports = function(grunt) { errorMessage(err); done(); } else { - template.templateId = response.TemplateId; - // append this record to the result array, used by postmark-templates-output task - var upd = grunt.config.get('updatedTemplates') || {}; - var tname = template.name; - delete template.name; - upd[tname] = template; - grunt.config.set('updatedTemplates', upd); - - done(template); + template.TemplateId = response.TemplateId; + // compile the templates for use by the `postmark-templates-output` task + var updatedTemplates = grunt.config.get('updatedTemplates') || {}; + updatedTemplates[template.Name] = template; + grunt.config.set('updatedTemplates', updatedTemplates); + + done(); } } @@ -96,18 +89,30 @@ module.exports = function(grunt) { // invoke this task after postmark-templates to get an output file containing the resulting template IDs // this is in the same format as the postmark-templates config. - grunt.registerTask('postmark-templates-output', 'writes out the resulting template IDs', function() { - + grunt.registerTask('postmark-templates-output', 'Write out the resulting template IDs', function () { var options = this.options({ - filename: "templates-output.json" + cleanOutput: false + }); + var updatedTemplates = grunt.config('updatedTemplates'); + var oldTemplates = grunt.file.read(options.outputFile); + + Object.keys(updatedTemplates).forEach(function (updatedTemplateKey) { + updatedTemplates[updatedTemplateKey] = Object.assign( + oldTemplates[updatedTemplateKey] || {}, + updatedTemplates[updatedTemplateKey] + ); + + if (options.cleanOutput) { + delete updatedTemplates[updatedTemplateKey].HtmlBody; + delete updatedTemplates[updatedTemplateKey].TextBody; + } }); - var results = grunt.config('updatedTemplates'); - - grunt.file.write(options.filename, JSON.stringify(results, null, 2)); - - grunt.log.writeln("Updated template information written to " + options.filename); + grunt.file.write(options.outputFile, JSON.stringify(updatedTemplates, null, 2)); + grunt.log.writeln("Updated template information written to " + options.outputFile); }); + // you can also get a JSON report of uploaded templates + grunt.registerTask('postmark-templates', ['postmark-templates-upload', 'postmark-templates-output']); }; diff --git a/tasks/grunt-postmark.js b/tasks/grunt-postmark.js index e29e81a..e076ac9 100644 --- a/tasks/grunt-postmark.js +++ b/tasks/grunt-postmark.js @@ -66,7 +66,13 @@ module.exports = function(grunt) { function handleResponse(err, response, done) { - var _ = err ? errorMessage(err) : successMessage(response); + if (err) { + errorMessage(err); + + } else { + successMessage(response); + } + done(); } From 253a5877784b0ab8e5bd31d18541ec08342f6b7f Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 10 Dec 2017 21:16:38 -0800 Subject: [PATCH 29/32] Further decouple `grunt-postmark` from `mailmason`. Per @derekrushforth's comments on https://github.com/wildbit/grunt-postmark/pull/3. [This](https://github.com/wildbit/grunt-postmark/pull/3#discussion_r153006973) will break things for folks who are already using these changes (@gcoombe and @jmas) as we're now looking for `lowerCamel` variable names instead of `Title` cased ones. Just means a quick find/replace for `Name`/`name, `Subject`/`subject`, etc. I've internally changed the implementation of `postmark-templates-upload` to support in-memory `htmlBody` and `textBody` as well as lazy loaded paths `htmlSrc` and `textSrc` as @hybernaut originally had it. I didn't actually notice how far my changes had deviated from the default options for the `postmark-templates-upload` task when I filed the PR, but I actually like the idea of loading the template files instead of expecting them to be passed along as task data. --- Gruntfile.js | 57 +++++++++++-------- example_config.json | 7 +-- example_secrets.json | 3 - tasks/grunt-postmark-templates.js | 94 +++++++++++++++++++------------ 4 files changed, 92 insertions(+), 69 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 9a10fc6..e699d6c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,50 +15,57 @@ module.exports = function (grunt) { ------------------------------------------------- */ postmark: { options: { - serverToken: "<%= secret.postmark.server_token %>", + serverToken: "<%= secret.postmark.server_token %>" }, email: { from: "<%= config.postmark.from %>", to: "<%= config.postmark.to %>", subject: "<%= config.postmark.subject %>", - src: ['test/email.html'] + src: ["test/email.html"] }, bulk: { from: "<%= config.postmark.from %>", to: "<%= config.postmark.to %>", subject: "<%= config.postmark.subject %>", - src: ['test/*.html'] + src: ["test/*.html"] } }, - // you can either specify the template configuration here, or in templates.json - 'postmark-templates-upload': config.templates && config.templates - ? config.templates.output_file || config.templates.file - : { - test_email: { - name: 'testing-postmark-templates-js1-' + new Date().valueOf(), - subject: 'Testing grunt-postmark-templates', - htmlBody: 'test/email.html', - textBody: 'test/email.txt', - }, - test_email_again: { - name: 'testing-postmark-templates-js2-' + new Date().valueOf(), - subject: 'Testing grunt-postmark-templates', - htmlBody: 'test/email.html', - textBody: 'test/email.txt', - } + "postmark-templates-upload": { + options: { + ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>" + }, + test_email: { + name: "testing-postmark-templates-js1-" + new Date().valueOf(), + subject: "Testing grunt-postmark-templates", + htmlSrc: "test/email.html", + textSrc: "test/email.txt" }, + test_email_again: { + name: "testing-postmark-templates-js2-" + new Date().valueOf(), + subject: "Testing grunt-postmark-templates (again)", + htmlSrc: "test/email.html", + textSrc: "test/email.txt" + }, + test_email_inline_body: { + name: "testing-postmark-templates-js3-" + new Date().valueOf(), + subject: "Testing grunt-postmark-templates (inline body)", + htmlBody: "

Another email test

", + textBody: "Hello from grunt-postmark-templates\n" + } + }, - 'postmark-templates-output': { + "postmark-templates-output": { options: { - outputFile: '<%= config.templates.output_file || config.templates.file %>', - cleanOutput: '<%= config.templates.clean_output %>' - } + cleanOutput: "<%= config.templates && config.templates.cleanOutput %>", + outputFile: "<%= config.templates && config.templates.outputFile %>", + ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>" + }, } }); - grunt.loadTasks('tasks'); + grunt.loadTasks("tasks"); - grunt.registerTask('default', ['postmark', 'postmark-templates']); + grunt.registerTask("default", ["postmark", "postmark-templates"]); }; diff --git a/example_config.json b/example_config.json index 10cfaf1..6bce653 100644 --- a/example_config.json +++ b/example_config.json @@ -2,9 +2,6 @@ // tracked in source control. // // 2. Delete these comments from the configuration so Grunt doesn't get confused -// -// 3. Visit the following page for details: -// https://github.com/wildbit/mailmason/wiki/Getting-Started#configjson { "postmark": { "from": "jane@example.com", @@ -12,6 +9,8 @@ "subject": "Test Email" }, "templates": { - "file": "templates.json" + "ephemeralUploadResultsProperty": "postmark-templates-upload-results", + "cleanOutput": true, + "outputFile": "templates.json" } } diff --git a/example_secrets.json b/example_secrets.json index bede227..57db34e 100644 --- a/example_secrets.json +++ b/example_secrets.json @@ -2,9 +2,6 @@ // tracked in source control. // // 2. Delete these comments from the configuration so Grunt doesn't get confused -// -// 3. Visit the following page for details: -// https://github.com/wildbit/mailmason/wiki/Getting-Started#secretsjson { "postmark": { "server_token": "YOUR_POSTMARK_SERVER_TOKEN_HERE" diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 721a684..00648f5 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -6,9 +6,16 @@ */ module.exports = function (grunt) { + var DEFAULT_EPHEMERAL_UPLOAD_RESULTS_PROPERTY = 'postmark-templates-upload-results'; + var DEFAULT_OUTPUT_FILE_NAME = 'templates.json'; + var DEFAULT_CLEAN_OUTPUT = false; + grunt.registerMultiTask('postmark-templates-upload', 'Create or update Postmark templates', function () { var done = this.async(); - var options = this.options(); + var options = this.options({ + ephemeralUploadResultsProperty: DEFAULT_EPHEMERAL_UPLOAD_RESULTS_PROPERTY + }); + var ephemeralUploadResultsProperty = options.ephemeralUploadResultsProperty || DEFAULT_EPHEMERAL_UPLOAD_RESULTS_PROPERTY; var template = this.data; var serverToken = options.serverToken || grunt.config('secret.postmark.server_token'); @@ -17,62 +24,67 @@ module.exports = function (grunt) { grunt.fail.warn('Missing Postmark server token \n'); } - if (!template.Name) { - grunt.fail.warn('Missing required template property "Name" \n'); + if (!template.name) { + grunt.fail.warn('Missing required template property "name" \n'); } - if (!template.Subject) { - grunt.fail.warn('Missing required template property "Subject" \n'); + if (!template.subject) { + grunt.fail.warn('Missing required template property "subject" \n'); } - if (!template.HtmlBody) { - grunt.log.error('Missing template property "HtmlBody" \n'); + if (!template.htmlBody && !template.htmlSrc) { + grunt.log.error('Missing template property "htmlBody" or "htmlSrc"\n'); } - if (!template.TextBody) { - grunt.log.error('Missing template property "TextBody" \n'); + if (!template.textBody && !template.textSrc) { + grunt.log.error('Missing template property "textBody" or "textSrc"\n'); } var postmark = require('postmark'); var client = new postmark.Client(serverToken); // read the referenced files, but hold on to the original filenames - var expanded = Object.assign({}, template); - - if (template.TemplateId) { - client.editTemplate(template.TemplateId, expanded, function (err, response) { + var expanded = { + Name: template.name, + Subject: template.subject, + HtmlBody: template.htmlBody || grunt.file.read(template.htmlSrc), + TextBody: template.textBody || grunt.file.read(template.textSrc), + TemplateId: template.templateId, + }; + + if (expanded.TemplateId) { + client.editTemplate(expanded.TemplateId, expanded, function (err, response) { if (err && err.code === 1101) { - grunt.log.warn('Template ' + template.TemplateId + ' not found, so attempting create'); - delete template.TemplateId; + grunt.log.warn('Template ' + expanded.TemplateId + ' not found, so attempting create'); + delete template.templateId; delete expanded.TemplateId; - client.createTemplate(expanded, function (err, response) { - grunt.log.writeln('Template ' + template.Name + ' created: ' + JSON.stringify(response.TemplateId)); - handleResponse(err, response, done, template); + client.createTemplate(expanded.TemplateId, function (err, response) { + grunt.log.writeln('Template ' + expanded.Name + ' created: ' + JSON.stringify(response.TemplateId)); + handleResponse(err, done, response, template, ephemeralUploadResultsProperty); }); } else { - grunt.log.writeln('Template ' + template.Name + ' updated: ' + JSON.stringify(response.TemplateId)); - handleResponse(err, response, done, template); + grunt.log.writeln('Template ' + expanded.Name + ' updated: ' + JSON.stringify(response.TemplateId)); + handleResponse(err, done, response, template, ephemeralUploadResultsProperty); } }); } else { client.createTemplate(expanded, function (err, response) { - grunt.log.writeln('Template ' + template.Name + ' created: ' + JSON.stringify(response.TemplateId)); - handleResponse(err, response, done, template); + grunt.log.writeln('Template ' + expanded.Name + ' created: ' + JSON.stringify(response.TemplateId)); + handleResponse(err, done, response, template, ephemeralUploadResultsProperty); }); } - }); - function handleResponse(err, response, done, template) { + function handleResponse(err, done, response, template, ephemeralUploadResultsProperty) { if (err){ errorMessage(err); done(); } else { - template.TemplateId = response.TemplateId; + template.templateId = response.TemplateId; // compile the templates for use by the `postmark-templates-output` task - var updatedTemplates = grunt.config.get('updatedTemplates') || {}; - updatedTemplates[template.Name] = template; - grunt.config.set('updatedTemplates', updatedTemplates); + var updatedTemplates = grunt.config.get(ephemeralUploadResultsProperty) || {}; + updatedTemplates[template.name] = template; + grunt.config.set(ephemeralUploadResultsProperty, updatedTemplates); done(); } @@ -91,10 +103,17 @@ module.exports = function (grunt) { grunt.registerTask('postmark-templates-output', 'Write out the resulting template IDs', function () { var options = this.options({ - cleanOutput: false + cleanOutput: DEFAULT_CLEAN_OUTPUT, + outputFile: DEFAULT_OUTPUT_FILE_NAME, + ephemeralUploadResultsProperty: DEFAULT_EPHEMERAL_UPLOAD_RESULTS_PROPERTY }); - var updatedTemplates = grunt.config('updatedTemplates'); - var oldTemplates = grunt.file.read(options.outputFile); + var ephemeralUploadResultsProperty = options.ephemeralUploadResultsProperty || DEFAULT_EPHEMERAL_UPLOAD_RESULTS_PROPERTY; + var outputFile = options.outputFile || DEFAULT_OUTPUT_FILE_NAME; + var cleanOutput = options.cleanOutput || DEFAULT_CLEAN_OUTPUT; + var updatedTemplates = grunt.config(ephemeralUploadResultsProperty); + var oldTemplates = grunt.file.exists(outputFile) + ? grunt.file.read(outputFile) + : {}; Object.keys(updatedTemplates).forEach(function (updatedTemplateKey) { updatedTemplates[updatedTemplateKey] = Object.assign( @@ -102,15 +121,16 @@ module.exports = function (grunt) { updatedTemplates[updatedTemplateKey] ); - if (options.cleanOutput) { - delete updatedTemplates[updatedTemplateKey].HtmlBody; - delete updatedTemplates[updatedTemplateKey].TextBody; + if (cleanOutput) { + delete updatedTemplates[updatedTemplateKey].htmlBody; + delete updatedTemplates[updatedTemplateKey].textBody; + delete updatedTemplates[updatedTemplateKey].htmlSrc; + delete updatedTemplates[updatedTemplateKey].textSrc; } }); - grunt.file.write(options.outputFile, JSON.stringify(updatedTemplates, null, 2)); - grunt.log.writeln("Updated template information written to " + options.outputFile); - + grunt.file.write(outputFile, JSON.stringify(updatedTemplates, null, 2)); + grunt.log.writeln("Updated template information written to " + outputFile); }); // you can also get a JSON report of uploaded templates From ee632f2d6e7f3b4ad6adc1fa0f6a88ad2c362c07 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 7 Jan 2018 21:54:36 -0800 Subject: [PATCH 30/32] Add `postmark-templates-parse` and `postmark-templates-from-file`. Support both the original task target based upload that `wildbit/mailmason#34` is dependent on, as well as a stripped down version of its [`postmark-templates-generate`](https://github.com/wildbit/mailmason/pull/34/commits/9af44cccec2f07475578de4e5d48a0e86210b2ed#diff-84d8d4faaac8ca7bfe33ab2e934307c1R9) task, which just blindly reads email template configurations and applies them as task target configuration for `postmark-templates-upload`. I'm not too keen on the duplication between this and mailmason, but I don't know how else I'd iron things out other than by making mailmason write a file in its `postmark-templates-generate`, but that seems overly wasteful. --- Gruntfile.js | 14 ++++++++++---- example_config.json | 2 +- example_templates.json | 20 ++++++++++++++++++++ tasks/grunt-postmark-templates.js | 20 ++++++++++++++++++-- 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 example_templates.json diff --git a/Gruntfile.js b/Gruntfile.js index e699d6c..a068a61 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -57,10 +57,16 @@ module.exports = function (grunt) { "postmark-templates-output": { options: { - cleanOutput: "<%= config.templates && config.templates.cleanOutput %>", - outputFile: "<%= config.templates && config.templates.outputFile %>", - ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>" - }, + cleanOutput: "<%= config.templates && config.templates.cleanOutput %>", + outputFile: "<%= config.templates && config.templates.outputFile || config.templates && config.templates.file %>", + ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>" + } + }, + + "postmark-templates-parse": { + options: { + inputFile: "<%= config.templates && config.templates.inputFile || config.templates && config.templates.file %>" + } } }); diff --git a/example_config.json b/example_config.json index 6bce653..04c79a0 100644 --- a/example_config.json +++ b/example_config.json @@ -11,6 +11,6 @@ "templates": { "ephemeralUploadResultsProperty": "postmark-templates-upload-results", "cleanOutput": true, - "outputFile": "templates.json" + "file": "templates.json" } } diff --git a/example_templates.json b/example_templates.json new file mode 100644 index 0000000..9ccda38 --- /dev/null +++ b/example_templates.json @@ -0,0 +1,20 @@ +{ + "test_email": { + "name": "testing-postmark-templates-js1", + "subject": "Testing grunt-postmark-templates", + "htmlSrc": "test/email.html", + "textSrc": "test/email.txt" + }, + "test_email_again": { + "name": "testing-postmark-templates-js2", + "subject": "Testing grunt-postmark-templates (again)", + "htmlSrc": "test/email.html", + "textSrc": "test/email.txt" + }, + "test_email_inline_body": { + "name": "testing-postmark-templates-js3", + "subject": "Testing grunt-postmark-templates (inline body)", + "htmlBody": "

Another email test

", + "textBody": "Hello from grunt-postmark-templates\n" + } +} \ No newline at end of file diff --git a/tasks/grunt-postmark-templates.js b/tasks/grunt-postmark-templates.js index 00648f5..2003fd9 100644 --- a/tasks/grunt-postmark-templates.js +++ b/tasks/grunt-postmark-templates.js @@ -10,6 +10,20 @@ module.exports = function (grunt) { var DEFAULT_OUTPUT_FILE_NAME = 'templates.json'; var DEFAULT_CLEAN_OUTPUT = false; + grunt.registerTask('postmark-templates-parse', 'Parse Postmark templates for update', function () { + var options = this.options({ + inputFile: DEFAULT_OUTPUT_FILE_NAME + }); + var templateObjects = grunt.file.readJSON(options.inputFile || DEFAULT_OUTPUT_FILE_NAME); + + grunt.config.set('postmark-templates-upload', Object.assign( + templateObjects, + { + options: grunt.config.get('postmark-templates-upload.options') + } + )); + }); + grunt.registerMultiTask('postmark-templates-upload', 'Create or update Postmark templates', function () { var done = this.async(); var options = this.options({ @@ -49,7 +63,7 @@ module.exports = function (grunt) { Subject: template.subject, HtmlBody: template.htmlBody || grunt.file.read(template.htmlSrc), TextBody: template.textBody || grunt.file.read(template.textSrc), - TemplateId: template.templateId, + TemplateId: template.templateId }; if (expanded.TemplateId) { @@ -134,5 +148,7 @@ module.exports = function (grunt) { }); // you can also get a JSON report of uploaded templates - grunt.registerTask('postmark-templates', ['postmark-templates-upload', 'postmark-templates-output']); + grunt.registerTask('postmark-templates-from-targets', ['postmark-templates-upload', 'postmark-templates-output']); + grunt.registerTask('postmark-templates-from-file', ['postmark-templates-parse', 'postmark-templates-from-targets']); + grunt.registerTask('postmark-templates', ['postmark-templates-from-targets']); }; From b7e7a4252054271f5abd84f74ee43707db116b97 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 7 Jan 2018 22:40:22 -0800 Subject: [PATCH 31/32] Add some documentation around `postmark-templates`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And its constituent and related tasks – `postmark-templates-upload`, `postmark-templates-output`, `postmark-templates-parse`, `postmark-templates-from-targets`, and `postmark-templates-from-file`. Per https://github.com/wildbit/grunt-postmark/pull/3#issuecomment-355379589. --- Gruntfile.js | 3 +- README.md | 194 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 187 insertions(+), 10 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index a068a61..a6ffb5f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,7 +33,8 @@ module.exports = function (grunt) { "postmark-templates-upload": { options: { - ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>" + ephemeralUploadResultsProperty: "<%= config.templates && config.templates.ephemeralUploadResultsProperty %>", + serverToken: "<%= secret.postmark.server_token %>" }, test_email: { name: "testing-postmark-templates-js1-" + new Date().valueOf(), diff --git a/README.md b/README.md index 9b9ce5a..f99dca1 100644 --- a/README.md +++ b/README.md @@ -25,35 +25,34 @@ You'll need to add a [`config.json`](https://github.com/wildbit/mailmason/wiki/G ## Postmark task _Run this task with the `grunt postmark` command._ -## Options +### Options - -### serverToken +#### serverToken Your server token can be found on your server’s credentials page on [Postmark’s](http://postmarkapp.com) app. Type: `String` -### from +#### from This is the from address you are using to send the email. This must be a confirmed address that's set up on [Postmark’s](http://postmarkapp.com) sender signatures. Type: `String` -### to +#### to The address you’re sending to. Type: `String` -### subject +#### subject Type: `String` -## Examples +### Examples -### Options specified through target +#### Options specified through target ```javascript grunt.initConfig({ @@ -69,7 +68,7 @@ grunt.initConfig({ }); ``` -### Specify options through targets or globally +#### Specify options through targets or globally Options specified through a target will always take precedence over global options. ```javascript @@ -91,4 +90,181 @@ grunt.initConfig({ } } }); +``` + +## Postmark templates task +_Run this task with the `grunt postmark-templates` command._ + +The `postmark-templates` task is an alias of the `postmark-templates-from-targets` task which is itself a two stepped task – `postmark-templates-upload` followed by `postmark-templates-output`. + +`postmark-templates` (`postmark-templates-from-targets`) is intended for programmatic usage from other grunt tasks. + +### `postmark-templates-upload` Targets + +#### name +The name of your template. + +Type: `String` + + +#### subject +The subject line of your template. + +Type: `String` + +#### htmlSrc +A path to the generated HTML for your template. *Not used if `htmlBody` is specified.* + +Type: `String` + + +#### textSrc +A path to the generated plain text for your template. *Not used if `textBody` is specified.* + +Type: `String` + +#### htmlBody +The generated HTML content of your template. *Not required if `htmlSrc` is specified.* + +Type: `String` + + +#### textBody +The generated plain text content of your template. *Not required if `textSrc` is specified.* + +Type: `String` + + +### `postmark-templates-upload` Options + +#### serverToken +Your server token can be found on your server’s credentials page on [Postmark’s](http://postmarkapp.com) app. + +Type: `String` + + +#### ephemeralUploadResultsProperty +This is the name of a temporary grunt task configuration property used to communicate the upload results between `postmark-templates-upload` and `postmark-templates-output` without having to write a temporary file. **This should be the same value as `ephemeralUploadResultsProperty` for `postmark-templates-output`.** + +Type: `String` + + +### `postmark-templates-output` Options + +#### outputFile +The name of a file to output the results of the upload to Postmark. + +Type: `String` + + +#### cleanOutput +If `true`, do not export `htmlBody`, `htmlSrc`, `textBody` or `textSrc` in the specified `outputFile`. + +Type: `Boolean` + + +#### ephemeralUploadResultsProperty +This is the name of a temporary grunt task configuration property used to communicate the upload results between `postmark-templates-upload` and `postmark-templates-output` without having to write a temporary file. **This should be the same value as `ephemeralUploadResultsProperty` for `postmark-templates-upload`.** + +Type: `String` + + +### Example + +```javascript +grunt.initConfig({ + 'postmark-templates-upload': { + options: { + serverToken: 'POSTMARK_API_TEST', + ephemeralUploadResultsProperty: 'temp' + }, + test_email: { + name: 'testing-postmark-templates-js1', + subject: 'Testing grunt-postmark-templates', + htmlSrc: 'test/email.html', + textSrc: 'test/email.txt' + }, + test_email_inline_body: { + name: 'testing-postmark-templates-js3', + subject: 'Testing grunt-postmark-templates (inline body)', + htmlBody: '

Another email test

', + textBody: 'Hello from grunt-postmark-templates\n' + } + }, + 'postmark-templates-output': { + options: { + cleanOutput: true, + outputFile: 'templates.json', + ephemeralUploadResultsProperty: 'temp' + } + } +}); +``` + +## Postmark templates (from file) task +_Run this task with the `grunt postmark-templates-from-file` command._ + +The `postmark-templates-from-file` task invokes the `postmark-templates` task with targets read from a JSON file (via `postmark-templates-parse`). + +This task is intended for standalone, manual usage. + + +### `postmark-templates-parse` Options + +#### inputFile +The name of a file that specifies templates for uploading to Postmark. These templates take the same shape as defined by `postmark-templates-upload`. This should usually be the same value as `outputFile` for `postmark-templates-output`. + +Type: `String` + + +### Example + +In your `Gruntfile`: + +```javascript +grunt.initConfig({ + 'postmark-templates-parse': { + options: { + inputFile: 'templates.json' + } + }, + 'postmark-templates-upload': { + options: { + serverToken: 'POSTMARK_API_TEST', + ephemeralUploadResultsProperty: 'temp' + } + }, + 'postmark-templates-output': { + options: { + cleanOutput: true, + outputFile: 'templates.json', + ephemeralUploadResultsProperty: 'temp' + } + } +}); +``` + +In the file specified by `inputFile`, in this case, `templates.json`: + +```json +{ + "test_email": { + "name": "testing-postmark-templates-js1", + "subject": "Testing grunt-postmark-templates", + "htmlSrc": "test/email.html", + "textSrc": "test/email.txt" + }, + "test_email_again": { + "name": "testing-postmark-templates-js2", + "subject": "Testing grunt-postmark-templates (again)", + "htmlSrc": "test/email.html", + "textSrc": "test/email.txt" + }, + "test_email_inline_body": { + "name": "testing-postmark-templates-js3", + "subject": "Testing grunt-postmark-templates (inline body)", + "htmlBody": "

Another email test

", + "textBody": "Hello from grunt-postmark-templates\n" + } +} ``` \ No newline at end of file From 2cfb716e4e1b489caf51d13e81f62d65c8b34c46 Mon Sep 17 00:00:00 2001 From: Randy Tarampi Date: Sun, 7 Jan 2018 22:46:11 -0800 Subject: [PATCH 32/32] Bump version to `0.0.8`. --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index caed3b0..f7bea9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grunt-postmark", - "version": "0.0.7", + "version": "0.0.8", "description": "Send emails through Postmark using GruntJS", "main": "Gruntfile.js", "homepage": "https://github.com/wildbit/grunt-postmark.git", @@ -18,7 +18,6 @@ "name": "Derek Rushforth", "email": "derek@wildbit.com" }, - "main": "Gruntfile.js", "license": "MIT", "devDependencies": { "grunt": "^1.0.1"