在等待会计自动化发票发送流程的同时,决定先自动化这一端能做的事情。俗话说,条条大路通罗马,选择了开发一个AWS Lambda函数,并订阅一个公共SNS主题来触发它。
开发一个简单的node.js函数来发送邮件是相对直接的。首先,需要安装两个模块:
npm install nodemailer
npm install nodemailer-smtp-transport
然后,函数如下:
var transporter = nodemailer.createTransport(smtpTransport({
host: 'email-smtp.eu-west-1.amazonaws.com',
port: 587,
auth: {
user: '{ACCESS KEY}',
pass: '{SECRET KEY}'
}
}));
var text = 'Hi, Invoice! Thanks!';
var mailOptions = {
from: 'from@me.net',
to: 'to@someone.net',
bcc: 'me2@me.com',
subject: 'Invoice',
text: text
};
transporter.sendMail(mailOptions, function(error, info){
if(error){
console.log(error);
} else {
console.log('Message sent');
}
});
部署时遇到挑战,因为脚本有一些依赖。如果选择在线编辑并粘贴脚本,可能会得到这样的错误:
"errorMessage": "Cannot find module 'nodemailer'"
但是,部署一个包含依赖的完整包非常简单。只需将文件夹中的所有内容(不包括文件夹本身)打包成zip文件并上传即可。这种方法的缺点是,不能再在线编辑代码。所以即使是修复一个微不足道的打字错误,也需要重新打包和上传。
一种简单的方法是使用Powershell调用方法并安排任务来运行脚本:
Invoke-LMFunction -FunctionName automatedEmails -AccessKey accessKey -SecretKey secretKey -Region eu-west-1
但不希望依赖任何机器(本地或EC2实例)。否则,可以用C#写几行代码来完成同样的工作。使用Lambda的想法是避免维护,并让一切运行在由AWS维护的基础设施上。
不幸的是,AWS没有提供简单的方法来安排Lambda函数的调用。为了简单起见,决定使用不可靠的城镇时钟(UTC),它本质上是一个每15分钟发送“敲钟”消息的公共SNS主题。
因为只需要一封邮件,不在乎它是否跳过一两下,只要它在一天中至少敲响一次。
当然,为了避免用邮件轰炸会计,必须维护一个状态,以便每个月只发送一封邮件。但Lambda函数必须是无状态的。一些替代方案是使用AWS S3或DynamoDB。因为只需要一个简单的整数值,决定存储在S3上的一个文本文件中。所以首先,下载日志文件并检查最后发送的邮件月份:
function downloadLog(next) {
s3.getObject({
Bucket: bucketName,
Key: fileName
}, next);
}
function checkDate(response, next) {
var currentDay = parseInt(event.Records[0].Sns.Message.day);
var currentMonth = parseInt(event.Records[0].Sns.Message.month);
var lastMailMonth = parseInt(response.Body.toString());
if(isNaN(lastMailMonth)) {
lastMailMonth = currentMonth - 1;
}
if((currentDay == targetDayOfMonth) && (currentMonth > lastMailMonth)) {
next();
} else {
context.done(null, 'No action needed');
}
}
所以把所有的东西放在一起,最终的代码是:
var bucketName = '{BUCKET_NAME}';
var fileName = 'mail-automation-last-sent-date.txt';
var targetDayOfMonth = 7;
exports.handler = function(event, context) {
var async = require('async');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
var nodemailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
var currentMonth;
async.waterfall([
function downloadLog(next) {
s3.getObject({
Bucket: bucketName,
Key: fileName
}, next);
},
function checkDate(response, next) {
var message = JSON.parse(event.Records[0].Sns.Message);
var currentDay = parseInt(message.day);
currentMonth = parseInt(message.month);
var lastMailMonth = parseInt(response.Body.toString());
if(isNaN(lastMailMonth)) {
lastMailMonth = currentMonth - 1;
}
if((currentDay == targetDayOfMonth) && (currentMonth > lastMailMonth)) {
next();
} else {
context.done(null, 'No action needed');
}
},
function sendMail(response, next) {
var transporter = nodemailer.createTransport(smtpTransport({
host: 'email-smtp.eu-west-1.amazonaws.com',
port: 587,
auth: {
user: '{ACCESS KEY}',
pass: '{SECRET KEY}'
}
}));
var text = 'Hi, Invoice! Thanks!';
var mailOptions = {
from: 'from@me.net',
to: 'to@someone.net',
bcc: 'me2@me.com',
subject: 'Invoice',
text: text
};
transporter.sendMail(mailOptions, function(error, info){
if(error){
console.log(error);
} else {
s3.putObject({
Bucket: bucketName,
Key: fileName,
Body: currentMonth.toString(),
ContentType: 'text/plain'
}, function(err, data) {
console.log('Updated log');
context.done(null, 'Completed');
});
}
});
} ], function (err) {
if(err) {
context.fail(err);
} else {
context.succeed('Success');
}
});
};
让看看它是否会帮助自动获得发票!
更好的方法是检查邮件中的发票,并只在未收到时发送。此外,在初始邮件之后发送一些提醒也是不错的。但作为新决议是逐步、渐进地前进,将其称为版本1.0,并将剩余的任务留给以后的版本。
主要目标是实现这个任务,而不必担心基础设施。仍然不用担心,这仅仅是因为一个好人(即Eric Hammond)决定为其他人设置一个公共服务。
在研究中,遇到了一些参考资料,说同样的任务可以使用AWS Simple Workflow (SWF)来完成。以前没有使用过这项服务。它看起来很复杂,感觉需要经历一个陡峭的学习曲线。在版本2中,应该看看SWF,它将...
允许处理复杂的工作流