├── .gitignore ├── package.json ├── LICENSE ├── smtp2http.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smtp2http", 3 | "version": "1.1.2", 4 | "description": "SMTP to HTTP gateway", 5 | "bin": { 6 | "smtp2http": "./smtp2http.js" 7 | }, 8 | "dependencies": { 9 | "smtp-protocol": "^2", 10 | "squabble": "^1", 11 | "request": "^2", 12 | "mailparser": "^0.5", 13 | "colors": "^1", 14 | "objektify": "^1", 15 | "tlsfs": "^1" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/Zingle/smtp2http.git" 20 | }, 21 | "author": "Richard Remer (reanjr@gmail.com)", 22 | "license": "proprietary", 23 | "bugs": { 24 | "url": "https://github.com/Zingle/smtp2http/issues" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Zingle, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /smtp2http.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var squabble = require("squabble").createParser(), 3 | smtp = require("smtp-protocol"), 4 | http = require("request"), 5 | tlsfs = require("tlsfs"), 6 | copy = require("objektify").copy, 7 | MailParser = require("mailparser").MailParser, 8 | args, tlsTokens, serverOpts = {}; 9 | 10 | // enable color support 11 | require("colors"); 12 | 13 | // setup CLI argument parsing 14 | squabble.shortOpts().longOpts().stopper() 15 | .option("-T", "--tls") 16 | .flag("-s", "-q", "--silent", "--quiet") 17 | .flag("-v", "--verbose") 18 | .list("-H", "--header") 19 | .required("ENDPOINT"); 20 | 21 | // parse and apply arguments 22 | args = squabble.parse(); 23 | serverOpts.endpoint = args.named.ENDPOINT; 24 | serverOpts.headers = {}; 25 | args.named["--header"].forEach(function(headerLine) { 26 | var name = headerLine.split(":")[0]; 27 | serverOpts.headers[name] = serverOpts.headers[name] || []; 28 | serverOpts.headers[name].push(headerLine.substr(name.length+1).trim()); 29 | }); 30 | 31 | // configure console output 32 | if (args.named["--quiet"]) { 33 | console.log = function() {}; 34 | console.error = function() {}; 35 | } else if (!args.named["--verbose"]) { 36 | console.log = function() {}; 37 | } 38 | 39 | if (args.named["--tls"]) { 40 | tlsPaths = args.named["--tls"].split(":"); 41 | copy(serverOpts, tlsfs.readCertsSync(tlsPaths)); 42 | } 43 | 44 | // create and start SMTP server 45 | smtp.createServer(serverOpts, function(req) { 46 | var id; 47 | 48 | // accept all incoming messages 49 | req.on("to", function(to, ack) { 50 | id = to; 51 | console.log("-->".yellow + "incoming message to " + to); 52 | ack.accept(); 53 | }); 54 | 55 | // send message to web endpoint 56 | req.on("message", function(stream, ack) { 57 | stream.pipe(new MailParser().on("end", function(email) { 58 | http.post({ 59 | url: serverOpts.endpoint, 60 | json: email, 61 | headers: serverOpts.headers 62 | }, function(err, res, body) { 63 | var msg; 64 | 65 | if (err) return console.error("error".red + " " + err.message); 66 | 67 | msg = String(res.statusCode); 68 | if (res.statusCode >= 500) { 69 | console.error(msg.red + " " + body.replace("\n", "\\n")); 70 | } else if (res.statusCode >= 200 && res.statusCode < 300) { 71 | console.log(msg.green + " message passed " + id); 72 | } else { 73 | console.error(msg.magenta + " unexpected"); 74 | } 75 | }) 76 | }).on("error", function(err) { 77 | console.error(err); 78 | })); 79 | 80 | ack.accept(); 81 | }); 82 | }).listen(process.env.SMTP_PORT || 25); 83 | 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smtp2http 2 | SMTP to HTTP gateway 3 | 4 | Usage 5 | ----- 6 | ```sh 7 | Usage: smtp2http [-v|--verbose] [-s|--silent|-q|--quiet] 8 | [-T|--tls=] [[-H|--header=
], ...] ENDPOINT 9 | 10 | -H --header=
HTTP header to send with requests 11 | -q --quiet Do not log to STDERR 12 | -s --silent Alias for --quiet 13 | -T --tls= colon-delimited list of cert files 14 | q.v. TLS Option below 15 | -v --verbose Log information to STDOUT 16 | 17 | TLS Option 18 | The --tls option accepts a colon-delimited list of certificate files. 19 | You can specify a single combined PFX file, a cert file followed by a 20 | key file, or a cert file followed by a key file followed by a signing 21 | authority certificate. 22 | ``` 23 | 24 | 25 | Examples 26 | -------- 27 | Begin listening for incoming SMTP messages, parse them, and post them to the 28 | specified HTTP endpoint. 29 | ```sh 30 | smtp2http https://example.com/foo 31 | ``` 32 | 33 | ### TLS Support 34 | Enable TLS using separate certificate and key files with signing CA cert. 35 | ```sh 36 | CERT=/etc/private/ssl/example.com.crt 37 | KEY=/etc/private/ssl/example.com.key 38 | CA=/etc/private/ssl/example.com-ca.crt 39 | smtp2http -T$CERT:$KEY:$CA https://example.com/foo 40 | ``` 41 | 42 | Enable TLS using cert and key files only. 43 | ```sh 44 | CERT=/etc/private/ssl/example.com.crt 45 | KEY=/etc/private/ssh/example.com.key 46 | smtp2http -T$CERT:$KEY https://example.com/foo 47 | ``` 48 | 49 | Enable TLS using single PFX combined cert file. 50 | ```sh 51 | CERT=/etc/private/ssl/example.com.pfx 52 | smtp2http -T$CERT https://example.com/foo 53 | ``` 54 | 55 | Install 56 | ------- 57 | ```sh 58 | git clone git@github.com:Zingle/smtp2http.git 59 | cd smtp2http 60 | npm install -g 61 | ``` 62 | 63 | Development 64 | ----------- 65 | The SMTP protocol does not provide any way to set the TCP port used for 66 | communication. Because of this, development can be difficult if trying to 67 | use public mail providers because you must have an internet facing server 68 | listening on port 25. 69 | 70 | You can run something like the following to set up a reverse tunnel using 71 | a public server to which you have SSH access. 72 | 73 | ```sh 74 | ssh -fNR 25:localhost:2025 root@example.com 75 | SMTP_PORT=2025 smtp2http 76 | ``` 77 | 78 | In this example, `example.com` is the public server to which you have access, 79 | `root` is a user with access to open low-numbered ports on that host, `25` is 80 | the port this host will listen on (standard SMTP port), and `2025` is the port 81 | `smtp2http` will listen on. 82 | 83 | Appendix - Generating a PFX cert 84 | -------------------------------- 85 | If you have a typical PEM cert with separate key and cert files, you may wish 86 | to generate a PFX cert which is simple to specify. 87 | 88 | ```sh 89 | openssl pkcs12 -export \ 90 | -out example.com.pfx \ 91 | -in example.com.crt -inkey example.com.key \ 92 | -certfile example.com-ca.crt 93 | ``` 94 | --------------------------------------------------------------------------------