Commit 0cb862d6 authored by kimi's avatar kimi

add cmd init, test, send, tail and tests

parent 42dea756
......@@ -9,3 +9,10 @@ node_modules
# Optional npm cache directory
.npm
#.muletter
.muletter
config.yml
attachments
list.txt
body.txt
body.html
......@@ -3,7 +3,7 @@
## Requirements
- [Nodejs v4](https://nodejs.org)
- [Nodejs v6](https://nodejs.org)
- SMTP server
## Install or Update
......@@ -25,17 +25,19 @@ Note that if already you use [MuList](https://github.com/kimihub/mulist) to mana
Example of `config.yml`:
smtp_user: username
smtp_password: password
smtp_auth:
user: username
pass: password
smtp_host: smtp.hostname.com
smtp_ssl: true
letter_from: letter name <username@hostname.com>
letter_subject: subject letter
If you use a service included in [nodemailer-wellknown](https://github.com/nodemailer/nodemailer-wellknown) module you can simply fill `smtp_service` instead of 'smtp_host' and 'smtp_ssl':
All parameters prefixed by `smtp_` match [SMTP transport options](https://nodemailer.com/smtp) of nodemailer.
smtp_user: username
smtp_password: password
So you can also use a [well-known provider](https://nodemailer.com/smtp/well-known/) included in nodemailer :
[...]
smtp_service: Godaddy
letter_from: letter name <username@hostname.com>
letter_subject: subject letter
......@@ -77,6 +79,20 @@ Example of `list.txt`:
init Check config.yml list.txt body.txt attachments
test <email> Send the letter to <email>
send Send the letter to list.txt
tail Display last logs
help [command] Output usage information of [command]
[...]
## New letter => New list.txt
Note that after each sent, any address of `list.txt` will be removed where mail sending succeed. For every new letter, a new mailing list should be exported.
## Exit process or failed sending
If the process exit by accident after run the command `muletter send` or if any mail sending failed caused by many reasons (SMTP or internet connection unreachable), just run again `muletter send` to continue or send again failed mail sending.
## Power outages or freeze
This program does not writeStream `list.txt`, so you must protect your computer or laptop against sudden power outages or freeze to avoid several sending to a same address and your SMTP blacklisted.
If this happens, you might not run `muletter send` again. You just have to remove the file `.muletter/lockfile`.
'use strict';
const fs = require('fs');
const path = require('path');
const nodemailer = require('nodemailer');
const red = s => (`\x1b[38;5;01m${s}\x1b[0m`);
const green = s => (`\x1b[38;5;02m${s}\x1b[0m`);
const fail = (code, msg) => console.error(`${red(code)} ${msg}`);
const done = s => console.log(`${green('✓')} ${s}`);
var transporter, transporterOptions = {}, letterOptions = {}, letterList = [], letterListFail = [], cursor = 0, lockpath, logpath, filelog = [];
const init = (cb, test) => {
const cwd = process.cwd();
const workdirpath = path.resolve(cwd, '.muletter');
const listpath = path.resolve(cwd, 'list.txt');
const configpath = path.resolve(cwd, 'config.yml');
const textpath = path.resolve(cwd, 'body.txt');
const htmlpath = path.resolve(cwd, 'body.html');
const attachpath = path.resolve(cwd, 'attachments');
let config = {}, errors = 0;
// create working directory
if (!fs.existsSync(workdirpath)) {
fs.mkdirSync(workdirpath);
}
lockpath = path.resolve(workdirpath, 'lockfile');
if (fs.existsSync(lockpath)) {
fail('ERR', 'A letter is sending');
process.exit(1);
}
if (!fs.existsSync(configpath)) {
fail('ERR', 'config.yml is missing');
errors++;
} else {
// parse YAML
let multiple = false;
fs.readFileSync(configpath, 'utf8').split('\n').forEach(line => {
let arrayLine = line.split(':');
if (multiple && (arrayLine[0].indexOf('\t') !== -1 || arrayLine[0].indexOf(' ') !== -1)) {
config[multiple][arrayLine[0].replace('\t','').trim()] = arrayLine[1].trim();
} else {
multiple = false;
if (arrayLine[0] && arrayLine[1]) {
let index, value;
index = arrayLine[0].trim();
value = arrayLine[1].trim();
if (value === '') {
multiple = index;
config[index] = {};
} else {
config[index] = value;
}
}
}
});
if (!config.letter_from) {
errors++;
fail('ERR', '`letter_from` is not defined in config.yml');
}
if (!config.letter_subject) {
errors++;
fail('ERR', '`letter_subject` is not defined in config.yml');
}
if (!config.smtp_service && !config.smtp_host) {
errors++;
fail('ERR', '`smtp_service` or `smtp_host` are not defined in config.yml');
}
}
if (!test) {
if (!fs.existsSync(listpath)) {
fail('ERR', 'list.txt is missing');
errors++;
}
else {
letterList = fs.readFileSync(listpath, 'utf8').trim('\n').split('\n');
if (letterList.length === 0) {
fail('ERR', 'list.txt is empty');
errors++;
} else {
done(`list.txt: ${letterList.slice(0,10)}[...]`);
}
}
}
if (!fs.existsSync(textpath) && !fs.existsSync(htmlpath)) {
fail('ERR', 'body.txt or body.html is missing');
errors++;
}
if (fs.existsSync(textpath)) {
letterOptions.text = fs.createReadStream(textpath, 'utf8');
done(`body.txt`);
}
if (fs.existsSync(htmlpath)) {
letterOptions.html = fs.createReadStream(htmlpath, 'utf8');
done(`body.html`);
}
if (fs.existsSync(attachpath)) {
letterOptions.attachments = fs.readdirSync(attachpath).map(file => {
done('attachment: ' + file);
return {
filename: file,
path: path.resolve(attachpath, file)
}
});
}
if (errors > 0) {
process.exit(1);
}
// letter options
letterOptions.from = config.letter_from;
done(`from: ${letterOptions.from}`);
letterOptions.subject = config.letter_subject;
done(`subject: ${letterOptions.subject}`);
// logpath
logpath = path.resolve(workdirpath, 'logs.txt');
// SMTP options
Object.keys(config).forEach(param => {
if (param.indexOf('smtp_') !== -1) {
transporterOptions[param.slice(5)] = config[param];
}
});
if (!test) transporterOptions.pool = true;
// verify smtp configuration
transporter = nodemailer.createTransport(transporterOptions);
console.log('Verifying SMTP...');
transporter.verify((error, success) => {
if (error) {
fail('ERR', error);
process.exit(1);
} else {
done(`SMTP server`);
// trigger all Signal Events
process.on('SIGINT', () => { process.exit() });
process.on('SIGILL', () => { process.exit() });
process.on('SIGHUP', () => { process.exit() });
process.on('SIGBREAK', () => { process.exit() });
process.on('exit', () => {
if (cursor > 0) {
let list = letterListFail.concat(letterList.slice(cursor));
fs.writeFileSync(listpath, list.join('\n'));
if (filelog.length > 0) {
fs.writeFileSync(logpath, filelog.join('\n'));
}
}
if (fs.existsSync(lockpath)) fs.unlinkSync(lockpath);
});
if (cb) cb();
}
});
}
module.exports.init = init;
module.exports.test = (email, cb) => {
init(() => {
letterOptions.to = email;
transporter.sendMail(letterOptions, (error, info) => {
if (error) {
fail('ERR', error);
process.exit(1);
} else {
done(`Response: ${info.response}`);
if (cb) cb();
}
})
}, true);
}
module.exports.send = cb => {
init(() => {
const stdin = process.stdin;
console.log('Send this letter to all address of [list.txt] ? Yes-No[Default]');
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', key => {
if (key.toLowerCase().indexOf('yes') === -1) {
process.exit();
}
fs.writeFileSync(lockpath, '');
letterList.forEach((email, i) => {
letterOptions.to = email;
transporter.sendMail(letterOptions, (error, info) => {
cursor++;
if (error) {
letterListFail.push(email);
filelog.push(error);
fail('ERR', error);
} else {
done(email);
}
if ((cursor === letterList.length) && cb) {
cb();
}
})
});
});
});
}
module.exports.tail = cb => {
const logspath = path.resolve(process.cwd(), '.muletter/logs.txt');
if (fs.existsSync(logspath)) {
console.log(fs.readFileSync(logspath, 'utf8'));
}
if (cb) cb();
}
......@@ -9,8 +9,9 @@ ${description}
Commands
init Check config.yml list.txt body.txt attachments
test <email> Send the letter to <email>
send Send the letter to list.txt
test <email> Send letter to <email>
send Send letter to list.txt
tail Display last logs
help [command] Output usage information of [command]
Options
......@@ -26,7 +27,7 @@ Required files
Optional attachments
attachments/*.*
attachments/*
`;
module.exports.init = `
......@@ -35,8 +36,9 @@ Check if all parameters are defined in config.yml, if body.(txt|html), list.txt
Example of config.yml
smtp_user: username
smtp_password: password
smtp_auth:
user: username
pass: password
smtp_service: GoDaddy
letter_from: letter name <username@hostname.com>
letter_subject: subject letter
......@@ -51,4 +53,10 @@ module.exports.send = `
Usage: muletter send
Send the letter test to list.txt
`
module.exports.tail = `
Usage: muletter tail
Display last logs
`
module.exports.version = version;
#!/usr/bin/env node
'use strict';
const MuletterCmd = require('./muletter-cmd');
const MuletterHelp = require('./muletter-help');
const cmd = process.argv[2];
const arg = process.argv[3];
const program = require('commander');
const config = require('./package');
console.error = (arg1, arg2) => console.warn(arg1?`\x1b[38;5;01m${arg1}\x1b[0m`:'', arg2? arg2:'');
program
.description(config.description)
.usage('[options] [command] [argument]')
.version(config.version)
.command('init', 'Check config.yml, list.txt, body.(txt|html) and *.(jpg|png|pdf|zip...) as attachments ')
.command('send', 'Send the letter after running `init` command')
.on('--help', () => {
console.log(' Example of config.yml:', '\n');
console.log(' smtp_host: smtp.provider.com');
console.log(' smtp_user: username');
console.log(' smtp_password: 620f921w0212z4');
console.log(' letter_from: from@provider.com');
console.log(' letter_subject: this is the subject');
console.log('\n');
})
.parse(process.argv);
// no cmd or option
if (!cmd) {
console.log(MuletterHelp.main);
process.exit();
}
if (process.argv[2] === 'init') {
// output help option
if (['-h', '--help'].indexOf(cmd) !== -1) {
console.log(MuletterHelp.main);
process.exit();
}
if (process.argv[2] === 'send') {
// output version option
if (['-V', '--version'].indexOf(cmd) !== -1) {
console.log(MuletterHelp.version);
process.exit();
}
if ('init' === cmd) {
if (arg) {
console.log(MuletterHelp.init);
process.exit();
}
MuletterCmd.init(process.exit);
}
if ('test' === cmd) {
if (!arg) {
console.log(MuletterHelp.test);
process.exit();
}
MuletterCmd.test(arg, process.exit);
}
if ('send' === cmd) {
if (arg) {
console.log(MuletterHelp.send);
process.exit();
}
MuletterCmd.send(process.exit);
}
if ('tail' === cmd) {
if (arg) {
console.log(MuletterHelp.tail);
process.exit();
}
MuletterCmd.tail(process.exit);
}
// output help arg[cmd]
if ('help' === cmd) {
if (!arg || !MuletterHelp[arg]) {
console.log(MuletterHelp.main);
process.exit();
}
console.log(MuletterHelp[arg]);
process.exit();
}
{
"name": "muletter",
"version": "0.1.5",
"version": "0.1.6",
"description": "CLI newsletter for SMTP",
"repository": {
"type": "git",
......@@ -27,16 +27,18 @@
"homepage": "https://github.com/kimihub/muletter#readme",
"preferGlobal": true,
"bin": {
"mulist": "muletter.js"
"muletter": "muletter.js"
},
"engines": {
"node": ">= 4.0.0"
"node": ">=6.0.0"
},
"dependencies": {
"nodemailer": "^2.7.2",
"nodemailer-wellknown": "^0.2.1"
"nodemailer": "^3.0"
},
"devDependencies": {
"maildev": "^0.14.0"
},
"scripts": {
"test": "node test"
}
}
'use strict';
const MailDev = require('maildev');
const MuLetter = require('../muletter-cmd.js');
const fs = require('fs');
const cmd = process.argv[2];
const arg = process.argv[3];
var config, body, list, maildev;
config = `
smtp_host: localhost
smtp_port: 1025
smtp_ignoreTLS: true,
letter_from: expeditor@provider.com
letter_subject: letter test
`;
body = `
lorem ipsum doloris
`;
list = `expeditor@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
expeditor2@provider.com
expeditor3@provider.com
expeditor4@provider.com
`;
// add config letter & smtp files
fs.writeFileSync('config.yml', config);
fs.writeFileSync('body.txt', body);
fs.writeFileSync('list.txt', list);
// maildev
maildev = new MailDev({
outgoingHost: 'localhost',
outgoingPort: 1025,
});
maildev.listen(() => {
if (!cmd) process.exit();
if (cmd === 'test' && arg) {
maildev.on('new', function(email){
console.log(email);
process.exit();
});
MuLetter[cmd](arg);
return;
}
if (cmd === 'tail' || cmd === 'init') {
MuLetter[cmd](process.exit);
} else {
MuLetter[cmd](() => {
setTimeout(() => {
maildev.getAllEmail((err, emails) => {
if (err) return console.log(err);
console.log('There are %s emails received', emails.length);
process.exit();
});
}, 1000);
})
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment