Created
October 27, 2021 22:55
-
-
Save coolaj86/fd100ff34bf5416f9fa4e19712666ebb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
let request = require('@root/request'); | |
let Postmark = module.exports; | |
Postmark._serverToken = process.env.POSTMARK_SERVER_TOKEN || ''; | |
// Divide streams into Transactional vs Bulk and Important vs Casual | |
Postmark.streams = { | |
// transactional, important | |
account: { | |
id: process.env['POSTMARK_STREAM_ACCOUNT_ID'] || '', | |
from: process.env['POSTMARK_STREAM_ACCOUNT_FROM'] || '', | |
}, | |
// transactional, casual | |
activity: { | |
id: process.env['POSTMARK_STREAM_ACTIVITY_ID'] || '', | |
from: process.env['POSTMARK_STREAM_ACTIVITY_FROM'] || '', | |
}, | |
// bulk, important | |
service: { | |
id: process.env['POSTMARK_STREAM_SERVICE_ID'] || '', | |
from: process.env['POSTMARK_STREAM_SERVICE_FROM'] || '', | |
}, | |
// bulk, casual | |
promo: { | |
id: process.env['POSTMARK_STREAM_PROMO_ID'] || '', | |
from: process.env['POSTMARK_STREAM_PROMO_FROM'] || '', | |
}, | |
}; | |
Postmark._defaultStream = Postmark.streams.activity; | |
// See "Forbidden File Types" in https://postmarkapp.com/developer/user-guide/send-email-with-api | |
Postmark._forbidden = | |
'vbs, exe, bin, bat, chm, com, cpl, crt, hlp, hta, inf, ins, isp, jse, lnk, mdb, pcd, pif, reg, scr, sct, shs, vbe, vba, wsf, wsh, wsl, msc, msi, msp, mst' | |
.split(/[,\s\n\r]+/) | |
.filter(Boolean); | |
Postmark.send = async function (mgMsg) { | |
let pmMsg = await Postmark._mg2pmWithAttachments(mgMsg); | |
if (mgMsg.stream) { | |
let stream = Postmark.streams[mgMsg.stream] || Postmark._defaultStream; | |
pmMsg.MessageStream = stream.id; | |
pmMsg.From = stream.from; | |
} | |
if (!pmMsg.MessageStream) { | |
pmMsg.MessageStream = Postmark._defaultStream.id; | |
} | |
let resp = await request({ | |
url: 'https://api.postmarkapp.com/email', | |
method: 'POST', | |
headers: { | |
'X-Postmark-Server-Token': Postmark._serverToken, | |
}, | |
json: pmMsg, | |
}); | |
if (resp.statusCode >= 300) { | |
var err = new Error('failed to send message'); | |
//@ts-ignore | |
err.response = resp.toJSON(); | |
//@ts-ignore | |
err.response.request.headers['X-Postmark-Server-Token'] = err.response.request.headers['X-Postmark-Server-Token'].replace(/\w/g, '*'); | |
throw err; | |
} | |
return resp; | |
}; | |
Postmark._mg2pm = function (mgMsg) { | |
let trackOpens = mgMsg['o:tracking'] || mgMsg['o:tracking-opens']; | |
if (trackOpens && !['no', 'false'].includes(trackOpens)) { | |
trackOpens = true; | |
} | |
let trackLinks = mgMsg['o:tracking'] || mgMsg['o:tracking-clicks']; | |
if (trackLinks && !['no', 'false'].includes(trackLinks)) { | |
trackLinks = 'HtmlOnly'; | |
} | |
let pmMsg = { | |
From: mgMsg.from, | |
To: mgMsg.to, | |
Cc: mgMsg.cc, | |
Bcc: mgMsg.bcc, | |
Subject: mgMsg.subject, | |
//Tag: "Invitation", | |
HtmlBody: mgMsg.html, | |
TextBody: mgMsg.text, | |
ReplyTo: mgMsg['h:Reply-To'], | |
/* | |
Metadata: { Color: 'blue', 'Client-Id': '12345' }, | |
*/ | |
Headers: [ | |
/* { Name: 'CUSTOM-HEADER', Value: 'value' }, */ | |
], | |
Attachments: [ | |
/* { "Name": "readme.txt", "Content": "dGVzdCBjb250ZW50", "ContentType": "text/plain" } */ | |
], | |
TrackOpens: trackOpens, | |
TrackLinks: trackLinks, | |
MessageStream: 'outbound', | |
}; | |
// copy 'h:'-prefixed headers | |
Object.keys(mgMsg).forEach(function (k) { | |
if ('h:' !== k.slice(0, 2)) { | |
return; | |
} | |
if ('h:Reply-To' === k) { | |
return; | |
} | |
let header = { | |
Name: k.slice(2), | |
Value: mgMsg[k], | |
}; | |
//@ts-ignore | |
pmMsg.Headers.push(header); | |
}); | |
return pmMsg; | |
}; | |
Postmark._mg2pmWithAttachments = async function (mgMsg) { | |
let pmMsg = await Postmark._mg2pm(mgMsg); | |
// this is a very sad way to handle attachments... | |
if (!mgMsg.attachment || !mgMsg.attachment.length) { | |
return pmMsg; | |
} | |
let Fs = require('fs'); | |
let Path = require('path'); | |
mgMsg.attachment.forEach(function (v) { | |
let filename = v; | |
if ('string' !== typeof v) { | |
filename = v.options.filename; | |
} | |
let ext = Path.extname(filename).slice(1).toLowerCase(); | |
if (Postmark._forbidden.includes(ext)) { | |
let msg = `attachments of type '.${ext}' are not allowed`; | |
let err = new Error(''); | |
//@ts-ignore | |
err.code = 'INTERNAL_SERVER_ERROR'; | |
//@ts-ignore | |
err.source = 'mg2pm'; | |
//@ts-ignore | |
err.status = 500; | |
err.message = msg; | |
throw err; | |
} | |
}); | |
await mgMsg.attachment.reduce(async function (p, v) { | |
await p; | |
if ('string' === typeof v) { | |
let r = Fs.createReadStream(v); | |
let filename = Path.basename(v); | |
v = { | |
value: r, | |
options: { | |
filename: filename, | |
}, | |
}; | |
} | |
let buf = await promisifyStream(v.value); | |
pmMsg.attachments.push({ | |
Name: v.options.filename || v.value.path, | |
Content: buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''), | |
ContentType: v.options.contentType || 'application/octet-stream', | |
}); | |
}, Promise.resolve()); | |
return pmMsg; | |
}; | |
async function promisifyStream(r) { | |
if (r instanceof Buffer) { | |
return r; | |
} | |
return await new Promise(function (resolve) { | |
let body; | |
let chunks = []; | |
r.on('readable', function () { | |
let chunk; | |
while ((chunk = r.read())) { | |
chunks.push(chunk); | |
} | |
}); | |
r.on('end', function () { | |
body = Buffer.concat(chunks); | |
}); | |
resolve(body); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment