折腾:
【已解决】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”: “[email protected]”,
“username” : “green waste”
},
{
“email”: “[email protected]”,
“username” : “Crifan Li”
}
]
改为非ASCII的中文:
“receivers”: [
{
“email”: “[email protected]”,
“username” : “绿色垃圾”
},
{
“email”: “[email protected]”,
“username” : “克瑞芬”
}
看看发出来的邮件的标题是什么样的:
果然,收件人的字符串:
u’绿色垃圾 <[email protected]>, 克瑞芬 <[email protected]>’
变成乱码了:
所以非ASCII还是要去通过utf-8编码才对。
然后此处,对于多个收件人,直接用逗号拼接后的name和address的组合,去用Header去encode,结果是:
u’绿色垃圾 <[email protected]>, 克瑞芬 <[email protected]>’
编码为:
=?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 <[email protected]>
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 <[email protected]>, 克瑞芬 <[email protected]>' -> =?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'绿色垃圾 <[email protected]>' -> '=?utf-8?b?57u/6Imy5Z6D5Zy+?= <[email protected]>' :param nameAndAddress: :param encode: :return: """ (nameUnicode, addrUnicode) = parseaddr(nameAndAddress) nameStr = nameUnicode.encode(encode) # '绿色垃圾' addrStr = addrUnicode.encode(encode) # '[email protected]' formatedNameHeaderUnicode = formatEmailHeader(nameStr) formatedNameHeaderStr = formatedNameHeaderUnicode.encode(encode) # =?utf-8?b?57u/6Imy5Z6D5Zy+?= formatedNameAndAddress = formataddr((formatedNameHeaderStr, addrStr)) # '=?utf-8?b?57u/6Imy5Z6D5Zy+?= <[email protected]>' 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 # [email protected] -> 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+?= <[email protected]>, =?utf-8?b?5YWL55Ge6Iqs?= <[email protected]>' mergedReceiversNameAddr = RECEIVER_SEPERATOR.join(receiverNameAddrList) # u'Crifan2003 <[email protected]>, 克瑞芬 <[email protected]>' # 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>