使用 smtplib 发送中文邮件,直接调用可用,放在视图函数中调用会出现编码错误 UnicodeEncodeError: 'ascii' codec can't encode characters...

最近用smtplib发送邮件验证码,遇到了玄学编码问题

verify_text = "您好,您的验证码是{code}"

def send_email(receiver, verify_code):
    try:
        message = MIMEText(verify_text.format(code = verify_code), 'html', 'utf-8')
        message['From'] = MAIL_USEERNAME
        message['To'] =  receiver
        message['Subject'] = Header("邮件验证码","utf-8")
        context = ssl.create_default_context()
        print(message.as_string()) 
        with smtplib.SMTP_SSL(MAIL_SERVER, MAIL_PORT, context = context) as server:
            server.login(MAIL_USEERNAME,MAIL_PASSWORD)  
            server.sendmail(MAIL_USEERNAME, receiver, message.as_string())
        return 0
    except Exception as e:
        print("mail error : ", e)
        return -1

这个函数直接发送邮件是没问题的,但是放在api里面调用,就会发生
'ascii' codec can't encode characters in position 219-220: ordinal not in range(128)
API的代码如下:

@app.route('/get_mail_verify', methods = ['GET'])
def mail_verify():
    email = request.values.get('email',type = str, default = None)
    if MyRedis.get(email) != None:
        verify_code = MyRedis.get(email)
    else:
        verify_code = get_verify_code()
        MyRedis.set(email, verify_code, REDIS_STAY_TIME)
    return_json = {'data':{}}
    if send_email(email, verify_code) == -1:
        #发送失败,可能是网络问题或者email有误
        return_json['code'] = 900
        return_json['data']['msg'] = "Email can't use or Network congestion"
        return jsonify(return_json)
    else:
        return_json['code'] = 200
        return_json['data']['msg'] = "Get verify code successfully"
        return jsonify(return_json)

不知道如何解决,只好放弃中文,写英文邮件,就不会出问题了。

编码问题吧。

加这个:

import sys
reload(sys)
sys.setdefaultencoding(‘utf8’)

试下呢?

这应该好像是python2的方法,python3的替代方法为

import importlib,sys
importlib.reload(sys)

但并不奏效。
再描述一下问题细节吧,send_email函数和verify_text写在Mail.py中,该函数在另一文件test.py中import是可以正常发送邮件的,但是放在api里调用就会出现编码问题。

麻烦发个完整错误回溯看看?

127.0.0.1 - - [24/Dec/2020 15:02:28] "GET /user/get_mail_verify?email=my_email@163.com HTTP/1.1" 500 -
Traceback (most recent call last):
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "D:\Anaconda3\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "D:\Anaconda3\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "D:\Anaconda3\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "d:\src\UserControler.py", line 46, in get_mail_verify
    if send_email(email, verify_code) == -1:
  File "d:\src\Mail.py", line 31, in send_email
    server.sendmail(MAIL_USEERNAME, receiver, message.as_string())
  File "D:\Anaconda3\lib\smtplib.py", line 855, in sendmail
    msg = _fix_eols(msg).encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 219-220: ordinal not in range(128)

Python 版本是多少?可以参考这个回答试试对正文字符串进行编码把 str 转换成 bytes(stmplib 期待的类型):

verify_text.format(code=verify_code).encode('utf-8')

根据我的一番调查,这里提供最小问题复现方法
编写Mail.py如下

verify_text = "您好,您的验证码是{code}"

def send_email(receiver, verify_code):
        message = MIMEText(verify_text.format(code = verify_code), 'html', 'utf-8')
        message['From'] = MAIL_USEERNAME
        message['To'] =  receiver
        message['Subject'] = Header("邮件验证码","utf-8")
        print(message.as_string()) 

如果直接调用这个函数,得到的输出为

Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
From: from@email
To: to@email
Subject: =?utf-8?b?6YKu5Lu26aqM6K+B56CB?=

5oKo5aW977yM5oKo55qE6aqM6K+B56CB5pivMTIzNDU2

而在Flask的api中调用,得到的是

Content-Type: text/html; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
From: from@email
To: to@email
Subject: =?utf-8?b?6YKu5Lu26aqM6K+B56CB?=

您好,您的验证码是049c8c

可以看到,Content-Transfer-Encoding发生了变化,而这直接导致了邮件无法发送。不知道这时为什么

1 Like

这样试了还是不可以,verify_text应该就是utf-8格式的没问题,最大的问题在于,直接调用该函数,得到的MIMEText的Content-Transfer-Encoding是base64,而Flask的Api中调用,Content-Transfer-Encoding就变成了8bit,而在smtplib.py源码中,传入的MIMEText中只能有ascii中的字符,否则就会出错。贸然修改源码中的encoding部分,将其改为msg = _fix_eols(msg).encode('utf-8')确实可以发送,但修改python源码也太离谱了,不知道哪里会出现别的问题。

OK,我周末有时间再实际调试和研究下。临时你可以考虑用 Flask-Mail 或其他扩展来实现发送邮件的功能。另外你的 Python 版本是?

python版本是3.7.0,flask版本是1.0.2。Flask-Mail使用的时候遇到一些困难所以才决定直接用smtplib的,目前已经改用英文邮件了。

噢,好的。

啊兄弟,我找到bug了,是因为我在写api的那个文件里(假设是api.py文件)写了from flask_mail import Message,我本来打算用flask_mail的,后来没有用,忘记把这个import注释掉了,它直接影响了MIMEText的初始化。

哈哈,恭喜!