Chapter 11 如何科学的发送邮件
无论是数据分析还是运维监控,最终的结果通常都会通过Email的形式来展示,girlfriend也对Email做了很好的支持,通过使用内置的send_mail
插件可以满足绝大多数的邮件发送场景。
配置
使用send_mail插件最先要做的事情就是配置smtp服务器,例如:
[smtp_test]
host=smtp.163.com
port=25
[email protected]
password=123456
接下来就可以在工作流中以test
这个名称来引用这个smtp服务器的连接对象了。
如果你希望以ssl加密的方式进行传输,那么只需要修改端口号以及增加一个ssl为true的配置项就可以了:
[smtp_test]
host=smtp.163.com
port=465
[email protected]
password=123456
ssl=true
基本使用
如果只是发送一封包含标题和正文的邮件,那么非常简单:
Job(
name="send_mail",
args={
"server": "test",
"receivers": "[email protected]",
"sender": "[email protected]",
"subject": u"生日快乐!",
"content": u"生日快乐!小泽同学!"
}
)
如果要群发呢?群发分为两种情况,一种是发送给多个人,并且能在收件人中看到所有收件人的地址;另一种是虽然发送给多个人,但是每个人的收件人只能看到自己或者同组。send_mail
对这两种方式都做了支持。
第一种:
from email.utils import COMMASPACE
...
Job(
name="send_mail",
args={
"server": "test",
"receivers": COMMASPACE.join([
"[email protected]",
"[email protected]",
"[email protected]"
]),
"sender": "[email protected]",
"subject": u"明天开会,别忘了早起",
"content": u"迟到的要给大家买好吃的啊!",
}
)
这样[email protected]收到了邮件后,会在收件人列表中同时看到[email protected],[email protected],这种情形适合于多人讨论的邮件,比如用户反馈之类。
第二种:
Job(
name="send_mail",
args={
"server": "test",
"receivers": [
"[email protected]",
"[email protected]",
"[email protected]"
],
"sender": "[email protected]",
"subject": lambda ctx, receiver: u"你好," + receiver
"content": lambda ctx, receiver: receiver + u",您的本月薪资是xxx"
}
)
receivers参数变成了一个列表,然后subject和content接受了函数对象,这样可以针对不同的收件人进行不同的内容渲染。通过这种方式,每个人收到的邮件都不同,并且在收件人处只能看到自己的地址。如果内容相同,subject和content也是可以使用字符串的。
这种场景非常适合工资单,看看你们的财务MM还在手发工资单吗?那你只需要使用read_excel
、html_table
和send_mail
这三个插件为她开发一个自动的工资单发送程序了,当然,前提是她要对你有足够的信任。
发送附件
一提到发送附件,你可能会想到需要层层组装的MIME对象,send_mail
对附件做了适当的抽象,你不必关心任何的MIME层次,只需要关心要发送的文件即可。
from girlfriend.plugin.mail import Attachment
...
Job(
name="send_mail",
args={
"server": "test",
"receivers": "[email protected]",
"sender": "[email protected]",
"subject": u"本月的运营报表",
"content": u"附件中,请查收",
"attachments": [
Attachment("opt_report.xlsx", "application/octet-stream", u"2016-03份运营报表.xlsx".encode("gb2312")),
]
}
)
对,重点就是Attachment对象,每个Attachment代表着一个附件文件,Attachment接受三个参数,分别是要作为附件的文件路径、文件的MIME类型、附件要向收件人展示的文件名。
如果有多个附件,那就在列表中添加多个Attachment对象吧。
同时Attachment还保留了一个小惊喜,第一个参数除了接受字符串表示的文件路径之外,还可以接受一个StringIO对象,这意味着你可以把内存中的一些数据作为附件直接发出去,而不必先去创建文件,再读取,再发送附件。
Attachment(StringIO("too simple!"),
"text/plain", u"文本.txt".encode("gb2312")),
自定义发送
虽然subject和content参数可以接受函数对象,但有时我们需要更加灵活的自定义,比如很多公司喜欢每天发送数据日报,日报中包含的数据和附件往往都是因人而异的,send_mail
允许你对这类场景自己去定义邮件模板,所谓的邮件模板,就是一个Mail对象,包含了很多未实现的属性,需要你自己去实现这些属性。
class MyMail(Mail):
def __init__(self, context, receiver):
super(MyMail, self).__init__(context, receiver)
@property
def sender(self):
return "[email protected]"
@property
def receiver_email(self):
return self._receiver.email
@property
def subject(self):
return u"新年快乐"
@property
def content(self):
return u"新年快乐,发大财!{}".format(self._receiver.name)
@property
def attachments(self):
return [
Attachment(StringIO("simple!"),
"text/plain", u"文本.txt".encode("gb2312")),
Attachment(StringIO("naive!"),
"text/plain", u"文本2.txt".encode("gb2312"))
]
class User(object):
def __init__(self, name, email):
self.name = name
self.email = email
...
Job(
name="send_mail",
args={
"server": "test",
"receivers": [
User(u"迟宏泽", "[email protected]"),
User(u"小白", "[email protected]")
],
"mail": MyMail
)
可以看到,现在receivers接受的是自定义的User对象,这意味着你可以直接将数据库中读取的对象作为receivers参数,MyMail中的构造方法会接受每次的User对象,并构建出一个新的MyMail对象,你可以基于User对象对MyMail中的各种属性进行灵活渲染。
其它
send_mail
并不会在应用的整个生命周期中一直保持SMTP连接,它会在每次调用插件时创建连接和会话,发送完毕之后会将连接释放。如果你要想减少创建连接和会话的次数,应该在一次Job执行时尽可能发送相对多的邮件。