折腾:
【已解决】Python中smtp如何发送多个收件人地址且带名字的且可以被格式化
后,需要继续研究,如何把From和To(可能多个收件人)去编码。
参考:
Python email.Utils.formataddr Examples
18.1.5. email.header: Internationalized headers — Python 2.7.14 documentation
然后继续去研究Header格式化From和 To
根据:
https://docs.python.org/2/library/email.header.html
的解释是:
最早的话,用的协议是RFC 822,那时都是ASCII字符串
后来扩展到更多国家使用,支持多国语言后,但是协议却还是要求必须要用(7-bit的)ASCII去传输
-》而不允许其他语言的非ASCII字符
-〉所以后来就出现了协议的变通,支持把非ASCII字符,编码,转换为,ASCII字符
-》对应的协议就是:RFC 2822兼容性的一些协议,包括:RFC 2045、RFC 2046、RFC 2047、RFC 2231
-》对应的Python中的email的库,支持这些协议。
-〉其中的Header就是可以实现这个转换功能:把非ASCII编码转换为ASCII
-〉既然涉及到编码转换,则对应的就有个encode编码参数,最常见的当然是utf-8编码了。
搞懂基本逻辑后,就可以去写代码了。
此处也故意去试试,把之前的收件人的名字从英文:
“receivers”: [
{
“email”: “green-waste@163.com”,
“username” : “green waste”
},
{
“email”: “admin@crifan.org”,
“username” : “Crifan Li”
}
]
改为非ASCII的中文:
“receivers”: [
{
“email”: “green-waste@163.com”,
“username” : “绿色垃圾”
},
{
“email”: “admin@crifan.org”,
“username” : “克瑞芬”
}
看看发出来的邮件的标题是什么样的:
果然,收件人的字符串:
u’绿色垃圾 <green-waste@163.com>, 克瑞芬 <admin@crifan.org>’
变成乱码了:

所以非ASCII还是要去通过utf-8编码才对。
然后此处,对于多个收件人,直接用逗号拼接后的name和address的组合,去用Header去encode,结果是:
u’绿色垃圾 <green-waste@163.com>, 克瑞芬 <admin@crifan.org>’
编码为:
=?utf-8?b?57u/6Imy5Z6D5Zy+IDxncmVlbi13YXN0ZUAxNjMuY29tPiwg5YWL55Ge6Iqs?=
=?utf-8?q?_=3Cadmin=40crifan=2Ecom=3E?=


编码后的完整的内容是:
Content-Type: text/html; charset=”utf-8″
MIME-Version: 1.0
Content-Transfer-Encoding: base64
From: Crifan2003 <crifan2003@163.com>
To: =?utf-8?b?57u/6Imy5Z6D5Zy+IDxncmVlbi13YXN0ZUAxNjMuY29tPiwg5YWL55Ge6Iqs?=
=?utf-8?q?_=3Cadmin=40crifan=2Ecom=3E?=
Subject: =?utf-8?b?W+mrmOS7t10g5qCH6aKY5bim5Lit5paHIERlbGwgWFBTIDEzIFhQUzkzNjAt?=
=?utf-8?q?5797SLV-PUS_Laptop?=
CjxodG1sPgogICAgPGJvZHk+CiAgICAgICAgPGgxPlvpq5jku7ddIOagh+mimOW4puS4reaWhyBE
ZWxsIFhQUyAxMyBYUFM5MzYwLTU3OTdTTFYtUFVTIExhcHRvcDwvaDE+CiAgICAgICAgPHA+Tm90
IGJ1eSA8YSBocmVmPSJodHRwczovL3d3dy5taWNyb3NvZnQuY29tL2VuLXVzL3N0b3JlL2QvZGVs
bC14cHMtMTMteHBzLTkzNjAtbGFwdG9wLXBjLzhxMTczODRncnozNy9HVjVEP2FjdGl2ZXRhYj1w
aXZvdCUyNTNhb3ZlcnZpZXd0YWIiPuagh+mimOW4puS4reaWhyBEZWxsIFhQUyAxMyBYUFM5MzYw
LTU3OTdTTFYtUFVTIExhcHRvcDwvYT4gZm9yIGN1cnJlbnQgcHJpY2UgPGI+JDY5OS4wMDwvYj4g
Jmd0OyBleHBlY3RlZCBwcmljZSA8Yj4kNTk5LjAwPC9iPjwvcD4KICAgICAgICA8cD5TbyBzYXZl
IGZvciBsYXRlciBwcm9jZXNzPC9wPgogICAgPC9ib2R5Pgo8L2h0bWw+Cg==
看看能否发送成功,收件方能否正常解析
结果是不行的:

虽然标题title等中文可以正常解析。但是name+address用逗号分隔的,没法直接解码。
看来真的需要用之前的,拆分后,分别去Header中去格式化才可以。
参考之前别人的代码:
name, addr = parseaddr(nameAndAddress)
return formataddr(( \
Header(name, ‘utf-8’).encode(), \
addr.encode(‘utf-8’) if isinstance(addr, unicode) else addr))
去试试
然后是可以的:
不过后续的调试出现个问题:
smtpObj.sendmail(sender, receiverList, msgStr)
File “/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/smtplib.py”, line 751, in sendmail
raise SMTPDataError(code, resp)
smtplib.SMTPDataError: (554, ‘DT:SPM 163 smtp8,DMCowADH57MSky5aS+kSDQ–.22544S2 1513001757,please see http://mail.163.com/help/help_spam_16.htm?ip=121.238.253.149&hostid=smtp8&time=1513001757‘)
去看:
“554 DT:SPM 发送的邮件内容包含了未被许可的信息,或被系统识别为垃圾邮件。请检查是否有用户发送病毒或者垃圾邮件;”
-》估计是最近连续发了好多封类似邮件的内容,所以导致被误判为垃圾邮件了。
把邮件标题和内容换一下试试
结果问题依旧。
接着:
【已解决】Python中smtp用163账号发送邮件出错:550 User has no permission
但是却还是554 :
raise SMTPDataError(code, resp)
smtplib.SMTPDataError: (554, ‘DT:SPM 163 smtp7,C8CowABXBOa5mS5a8VlHBg–.59457S2 1513003465,please see http://mail.163.com/help/help_spam_16.htm?ip=121.238.253.149&hostid=smtp7&time=1513003465‘)
再回头试试163的,看看单个163账号发送给其他单个账号时的from和to的Header编码是否正常。
【总结】
目前用代码,对于from和to都是单个发件人和收件人的话,是可以用Header正常编码的:
<code>
def formatEmailHeader(headerValue, encode="utf-8"):
"""
format non-ASCII email header value to RFC 2822-compliant
Example:
u'绿色垃圾' -> =?utf-8?b?57u/6Imy5Z6D5Zy+?=
u'Crifan2003 <crifan2003@163.com>, 克瑞芬 <admin@crifan.org>'
->
=?utf-8?b?Q3JpZmFuMjAwMyA8Y3JpZmFuMjAwM0AxNjMuY29tPiwg5YWL55Ge6IqsIDxh?=
=?utf-8?q?dmin=40crifan=2Ecom=3E?=
:param headerValue:
:param encode:
:return:
"""
encodedHeaderValue = Header(headerValue, encode)
return encodedHeaderValue
def formatEmailNameAddrHeader(nameAndAddress, encode="utf-8"):
"""
Example:
u'绿色垃圾 <green-waste@163.com>' -> '=?utf-8?b?57u/6Imy5Z6D5Zy+?= <green-waste@163.com>'
:param nameAndAddress:
:param encode:
:return:
"""
(nameUnicode, addrUnicode) = parseaddr(nameAndAddress)
nameStr = nameUnicode.encode(encode) # '绿色垃圾'
addrStr = addrUnicode.encode(encode) # 'green-waste@163.com'
formatedNameHeaderUnicode = formatEmailHeader(nameStr)
formatedNameHeaderStr = formatedNameHeaderUnicode.encode(encode) # =?utf-8?b?57u/6Imy5Z6D5Zy+?=
formatedNameAndAddress = formataddr((formatedNameHeaderStr, addrStr)) # '=?utf-8?b?57u/6Imy5Z6D5Zy+?= <green-waste@163.com>'
return formatedNameAndAddress
def sendEmail( sender, senderPassword, receiverList,
senderName="", receiverNameList= "",
smtpServer = "", smtpPort = None, useSSL=False,
type = "plain", title = "", body = ""):
"""
send email
:param sender:
:param senderPassword:
:param receiverList:
:param senderName:
:param receiverNameList:
:param smtpServer:
:param smtpPort:
:param type:
:param title:
:param body:
:return:
"""
logging.debug("sender=%s, senderName=%s, smtpServer=%s, smtpPort=%s, useSSL=%s, type=%s, title=%s, body=%s",
sender, senderName, smtpServer, smtpPort, useSSL, type, title, body)
logging.debug("receiverList=%s, receiverNameList=%s", receiverList, receiverNameList)
defaultPort = None
SMTP_PORT_NO_SSL = 25
SMTP_PORT_SSL = 465
if useSSL:
defaultPort = SMTP_PORT_SSL
else:
defaultPort = SMTP_PORT_NO_SSL
if not smtpPort:
smtpPort = defaultPort
# init smtp server if necessary
if not smtpServer:
# extract domain from sender email
# crifan2003@163.com -> 163.com
atIdx = sender.index('@')
afterAtIdx = atIdx + 1
lastDomain = sender[afterAtIdx:]
smtpServer = 'smtp.' + lastDomain
# smtpServer = "smtp.163.com"
# smtpPort = 25
# RECEIVER_SEPERATOR = '; '
RECEIVER_SEPERATOR = ', '
senderNameAddr = "%s <%s>" % (senderName, sender)
receiversAddr = RECEIVER_SEPERATOR.join(receiverList)
receiverNameAddrList = []
formatedReceiverNameAddrList = []
for curIdx, eachReceiver in enumerate(receiverList):
eachReceiverName = receiverNameList[curIdx]
eachNameAddr = "%s <%s>" % (eachReceiverName, eachReceiver)
eachFormatedNameAddr = formatEmailNameAddrHeader(eachNameAddr)
receiverNameAddrList.append(eachNameAddr)
formatedReceiverNameAddrList.append(eachFormatedNameAddr)
formatedReceiversNameAddr = RECEIVER_SEPERATOR.join(formatedReceiverNameAddrList) # '=?utf-8?b?57u/6Imy5Z6D5Zy+?= <green-waste@163.com>, =?utf-8?b?5YWL55Ge6Iqs?= <admin@crifan.org>'
mergedReceiversNameAddr = RECEIVER_SEPERATOR.join(receiverNameAddrList) # u'Crifan2003 <crifan2003@163.com>, 克瑞芬 <admin@crifan.org>'
# formatedReceiversNameAddr = formatEmailHeader(mergedReceiversNameAddr) #=?utf-8?b?Q3JpZmFuMjAwMyA8Y3JpZmFuMjAwM0AxNjMuY29tPiwg5YWL55Ge6IqsIDxh?=
# =?utf-8?q?dmin=40crifan=2Ecom=3E?=
# def _format_addr(nameAndAddress):
# """
# format email address
# :param nameAndAddress: email name and address
# :return:
# """
# name, addr = parseaddr(nameAndAddress)
# return formataddr(( \
# Header(name, 'utf-8').encode(), \
# addr.encode('utf-8') if isinstance(addr, unicode) else addr))
msg = MIMEText(body, _subtype=type, _charset="utf-8")
# msg["From"] = _format_addr(senderNameAddr)
# msg["To"] = _format_addr(receiversNameAddr)
msg["From"] = formatEmailHeader(senderNameAddr)
# msg["From"] = senderNameAddr
# msg["To"] = formatEmailHeader(formatedReceiversNameAddr)
# msg["To"] = formatedReceiversNameAddr
# msg["To"] = mergedReceiversNameAddr
# msg["To"] = formatEmailHeader(receiversAddr)
msg["To"] = formatEmailHeader(mergedReceiversNameAddr)
# titleHeader = Header(title, "utf-8")
# encodedTitleHeader = titleHeader.encode()
# msg['Subject'] = encodedTitleHeader
msg['Subject'] = formatEmailHeader(title)
# msg['Subject'] = title
msgStr = msg.as_string()
# try:
# smtpObj = smtplib.SMTP('localhost')
smtpObj = None
if useSSL:
smtpObj = smtplib.SMTP_SSL(smtpServer, smtpPort)
else:
smtpObj = smtplib.SMTP(smtpServer, smtpPort)
smtpObj.set_debuglevel(1)
smtpObj.login(sender, senderPassword)
# smtpObj.sendmail(sender, receiversAddr, msgStr)
smtpObj.sendmail(sender, receiverList, msgStr)
logging.info("Successfully sent email: message=%s", msgStr)
# except smtplib.SMTPException:
# logging.error("Fail to sent email: message=%s", message)
return
</code>