postmailer
Version:
HTTP POST -> SMTP proxy, as Express middleware
491 lines (428 loc) • 20.8 kB
HTML
<html>
<head>
<title>POST for {{name}}</title>
<style>
body {
max-width: 800px;
margin: auto;
font-family: 'Open Sans', Tahoma;
font-size: 11pt;
color: #111;
}
h1 {
font-size: 1.5em;
margin: 0.5em 0;
text-align: center;
}
h2 {
font-size: 1.2em;
margin: 1em 0em;
}
code {
display: inline-block;
padding: 0.3em 0.3em 0.1em 0.3em;
background-color: #EEE;
border-radius: 0.3em;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
box-shadow: 0px 1px #DDD;
}
pre {
margin: 0.5em 2em;
padding: 0;
font-size: inherit;
}
pre code {
display: block;
width: 100%;
overflow: auto;
}
.cmd-option {
color: #048;
}
.cmd-value {
color: #800;
}
.cmd-value-file {
color: #482;
}
.headers, .text, .email {
width: 100%;
padding: 0.5em 0em;
background-color: #FFF;
border: none;
border-top: 1px solid #DDD;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
box-shadow: 0px 1px #DDD;
font-size: inherit;
height: 5em;
}
.send {
width: 100%;
height: 2em;
padding:0;
color: #000;
background-color: #EEE;
border: 1px solid #DDD;
border-radius: 0.3em;
font-family: inherit;
font-size: inherit;
box-shadow: 0px 1px #DDD;
cursor: pointer;
}
.send:hover {
background-color: #DDD;
}
.log {
overflow: auto;
margin-top: 1em;
margin-bottom: 2em;
}
.log-send, .log-receive, .log-event {
clear: both;
display: block;
margin: 0.5em 0em;
white-space: pre-wrap;
max-width: 90%;
}
.log-send {
float: left;
background-color: #DEF;
}
.log-send.pending {
font-style: italic;
background-color: #EEE;
}
.log-receive, .log-event {
float: right;
}
.success {
background-color: #DDF8DD;
}
.error {
background-color: #F8DDDD;
}
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: auto;
width: auto;
padding: 1em;
text-align: center;
opacity: 0.9;
}
/* iframe layout */
.iframe-table {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-spacing: 0;
border-collapse: collapse;
font-size: inherit;
}
.iframe-table td {
padding: 0;
}
#email-row, .iframe .email {
height: 1.5em;
}
#text-row {
position: relative;
}
.iframe .text {
position: absolute;
top: 0;
left: 0;
height: 100%;
resize: none;
}
#send-row {
height: 2em;
}
.iframe .send {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.iframe .email, .iframe .text {
padding: 0.2em 0.3em;
}
</style>
</head>
<body data-url="{{url}}" data-ws-url="{{ws-url}}" data-name="{{name}}">
<h1>POST for <span class="self-name">{{name}}</span></h1>
<p>This is an HTTP POST endpoint. Any text/document you POST to this URL will be received by the user.</p>
<p>You must provide a <code>From:</code> header (borrowed from SMTP), and can include other mail headers as well.</p>
<h2>Use with cURL:</h2>
<p>To send a plain text message:</p>
<pre><code>curl <span class="cmd-option">--data-binary</span> <span class="cmd-value">"Hello, world"</span> <span class="cmd-option">--header</span> <span class="cmd-value">"Content-Type: text/plain"</span> \
<span class="cmd-option">--header</span> <span class="cmd-value">"From: Somebody <somebody@example.com>"</span> \
<span class="cmd-argument"><span class="self-url">{{url}}</span></span></code></pre>
<p>To send an inline picture:</p>
<pre><code>curl <span class="cmd-option">--data-binary</span> <span class="cmd-value-file">@small-image.png</span> <span class="cmd-option">--header</span> <span class="cmd-value">"Content-Type: image/png"</span> \
<span class="cmd-option">--header</span> <span class="cmd-value">"From: Somebody <somebody@example.com>"</span> \
<span class="cmd-argument"><span class="self-url">{{url}}</span></span></code></pre>
<p>To send a picture (or other document) as an attachment:</p>
<pre><code>curl <span class="cmd-option">--data-binary</span> <span class="cmd-value-file">@large-image.jpg</span> <span class="cmd-option">--header</span> <span class="cmd-value">"Content-Type: image/jpeg"</span> \
<span class="cmd-option">--header</span> <span class="cmd-value">"Content-Disposition: attachment; filename=\"large-image.jpg\""</span> \
<span class="cmd-option">--header</span> <span class="cmd-value">"From: Somebody <somebody@example.com>"</span> \
<span class="cmd-argument"><span class="self-url">{{url}}</span></span></code></pre>
<script>
(function () { // Wrap so that minifying is more effective
function createElement(tag, textOrProps, parent, before) {
var tagParts = tag.split('.');
var element = document.createElement(tagParts.shift());
element.className = tagParts.join(' ');
if (typeof textOrProps === 'string') {
element.appendChild(document.createTextNode(textOrProps));
} else {
for (var key in textOrProps || {}) {
element[key] = textOrProps[key];
}
}
parent = parent || document.body;
if (typeof before === 'number') {
before = parent.childNodes[before];
}
parent.insertBefore(element, before || null);
return element;
}
// Replace template values with JS so we can just use the template on its own
var selfUrl = location.href.replace(/[?#].*/, ''); // Strip query and hash
var selfUrlEscaped = selfUrl.replace(/&/g, '&').replace(/</g, '<');
var backupName = selfUrl.replace(/.*:\/\//, '');
var backupNameEscaped = backupName.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
document.title = document.title.replace(/\{\{name\}\}/g, backupName);
document.body.innerHTML = document.body.innerHTML.replace(/\{\{url\}\}/g, selfUrlEscaped).replace(/\{\{name\}\}/g, backupNameEscaped);
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
function postAjaxMessage(headers, text, callback) {
var request = new XMLHttpRequest();
var url = document.body.getAttribute('data-url');
if (!url || url[0] === '{') url = location.href.replace(/#.*/, '');
request.open('POST', url, true);
// headerObj is map of arrays/strings
var headerObj = headers;
if (typeof headers === 'string') {
headerObj = {};
headers = headers.split('\n');
for (var i = 0; i < headers.length; i++) {
var line = headers[i] + "";
var name = line.match(/^([a-zA-Z\-]*)\:/);
if (!name) continue;
name = name[1];
var value = line.substring(name.length + 1).replace(/^\s*/, '').replace(/\r/g, '');
headerObj[name] = (headerObj[name] || []).concat(value);
}
}
var headerText = '';
for (var name in headerObj) {
var value = headerObj[name] + "";
try {
request.setRequestHeader(name, value);
headerText += name + ': ' + value + '\n';
} catch (e) {
try {
console.error(e);
} catch (e) {}
continue;
}
}
request.onreadystatechange = function () {
if (request.readyState !== 4) return;
var error = null;
if (request.status >= 300 || request.status < 200) {
error = new Error(request.status + ' ' + request.statusText);
}
callback(error, request.responseText);
};
request.send(text);
return headerText + '\n' + text;
}
if (!inIframe()) {
var h2 = createElement('h2', 'Use with AJAX:');
var inputHeaders = createElement('textarea.headers', {value: 'Content-Type: text/plain\nFrom: ???'});
var inputText = createElement('textarea.text', {value: 'Hello, world'});
var sendButton = createElement('input.send', {type: 'submit', value: 'send'});
var logBox = createElement('div.log');
sendButton.onclick = function () {
var receiveEntry = createElement('code.log-receive', 0, logBox, 0);
var sentText = postAjaxMessage(inputHeaders.value, inputText.value, function (error, text) {
if (error) {
receiveEntry.className += ' error';
} else {
receiveEntry.className += ' success';
}
if (typeof text === 'string') {
receiveEntry.appendChild(document.createTextNode(text));
}
});
var sendEntry = createElement('code.log-send', sentText, logBox, receiveEntry);
};
var wsUrl = document.body.getAttribute('data-ws-url');
if (wsUrl && wsUrl[0] !== '{') {
setupWs(wsUrl);
}
} else {
// Sorry, but I need this to work in IE, okay?
document.body.innerHTML = '<table class="iframe-table" cellpadding=0 cellspacing=0 border=0>'
+ '<tr><td id="email-row">'
+ '<input type="text" id="email-input" class="email" placeholder="name@example.com" value="">'
+ '<tr><td id="text-row">'
+ '<textarea id="text-input" class="text" placeholder="Enter your message"></textarea>'
+ '<tr><td id="send-row">'
+ '<input type="submit" id="send-input" class="send" value="send">'
+ '</table>';
document.body.className = 'iframe';
var inputEmail = document.getElementById('email-input');
var inputText = document.getElementById('text-input');
var sendButton = document.getElementById('send-input');
sendButton.onclick = function () {
if (!/@/.test(inputEmail.value) && !/\:\/\//.test(inputEmail.value)) {
inputEmail.focus();
inputEmail.className = 'email error';
return;
} else {
inputEmail.className = 'email';
}
if (!inputText.value) {
inputText.focus();
return;
};
sendButton.disabled = true;
sendButton.value = 'sending...';
var sentText = postAjaxMessage({
from: inputEmail.value,
subject: '(message form)'
}, inputText.value, function (error, text) {
sendButton.value = "send";
sendButton.disabled = false;
var overlay = document.createElement('div');
overlay.className = 'overlay';
document.body.appendChild(overlay);
setTimeout(function () {
overlay.parentNode.removeChild(overlay);
}, 5000);
if (error) {
overlay.className += ' error';
} else {
overlay.className += ' success';
inputText.value = '';
}
if (typeof text === 'string') {
try {
text = JSON.parse(text);
} catch (e) {
//
}
overlay.appendChild(document.createTextNode(text));
}
});
};
}
function setupWs(wsUrl) {
var initialHeaders = 'Content-Type: text/plain';
var lastHeaders = initialHeaders;
var ws = null, pendingMessages = [];
function connectIfNeeded() {
if (ws) return;
function handleError(error) {
ws = null;
lastHeaders = initialHeaders;
createElement('code.log-event error', error.message || 'connection error', logBox, 0);
console.error(error);
}
try {
ws = new WebSocket(wsUrl);
} catch (e) {
return handleError(e);
}
ws.onerror = handleError;
ws.onclose = function () {
if (!ws) {
// Already exited, possibly due to error
return;
}
ws = null;
lastHeaders = initialHeaders;
createElement('code.log-event', 'connection closed', logBox, 0);
if (pendingMessages.length) connectIfNeeded();
};
ws.onopen = function () {
createElement('code.log-event', 'connection opened', logBox, 0);
sendIfReady();
}
ws.onmessage = function (event) {
var message = event.data;
if (typeof message !== 'string') {
var blob = message;
message += ' (' + message.size + ' bytes)';
if (typeof FileReader === 'function') {
var reader = new FileReader();
reader.onprogress = function () {
if (reader.result) {
postElement.innerHTML = '<I>Binary:</I>\n';
postElement.appendChild(document.createTextNode(reader.result));
}
};
reader.readAsBinaryString(blob);
}
}
var postElement = createElement('code.log-receive.success', message, logBox, 0);
};
}
function sendIfReady() {
if (!ws || ws.readyState !== 1) return;
while (pendingMessages.length) {
var obj = pendingMessages.shift();
ws.send(obj.message);
setTimeout(obj.callback, 0);
}
}
function sendMessage(message, headers, callback) {
if (headers && headers !== lastHeaders) {
lastHeaders = headers;
headers = (headers || '').replace(/\r?\n/g, '\r\n').replace(/(^\s+|\s+$)/g, '').replace(/(\r\n)+/, '\r\n');
if (headers) headers += '\r\n';
message = headers + '\r\n' + message;
} else if (/^([^\r\n]+\r?\n)*\r?\n/.test(message)) {
message = '\r\n' + message;
}
pendingMessages.push({
message: message,
callback: callback
});
connectIfNeeded();
sendIfReady();
return message;
}
/********/
var h2 = createElement('h2', 'Use with WebSockets');
var p1 = createElement('p', 'You can also connect using WebSockets: ');
createElement('code', wsUrl, p1);
createElement('p', 'If you specify a header block (separated by double-newline) it will replace the existing header block and be used for subsequent messages.');
var inputHeaders = createElement('textarea.headers', {value: 'Content-Type: text/plain\nFrom: ???'});
var inputText = createElement('textarea.text', {value: 'Hello, world'});
var sendButton = createElement('input.send', {type: 'submit', value: 'send'});
var logBox = createElement('div.log');
sendButton.onclick = function () {
var sentText = sendMessage(inputText.value, inputHeaders.value, function () {
sendEntry.parentNode.removeChild(sendEntry);
sendEntry.className = 'log-send';
logBox.insertBefore(sendEntry, logBox.childNodes[0]);
});
var sendEntry = createElement('code.log-send.pending', sentText, logBox, 0);
};
}
})();
</script>
</body>
</html>