send email

Posted by deng liuyan on August 8, 2018

​ 一直觉得发送邮件再简单不过,直到最近深陷几个邮件大坑,费了不少时间才爬出来,当然其中也不乏公司环境给我挖的坑,代码不断,填坑不止呀……

​ python发邮件主要是借助框架( 如Django),以及pure python,其实底层都一样。

​ 借助Django,具体settings及发送邮件code如下:

1
2
3
4
5
6
7
8
9
10
EMAIL_HOST_USER = '*****'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
EMAIL_FROM = EMAIL_HOST_USER
EMAIL_HOST = 'smtp.partner.outlook.cn'
EMAIL_HOST_PASSWORD = 'Pul13448'
#看邮箱配置
EMAIL_PORT = 587
#看邮箱配置
EMAIL_USE_TLS = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
1
2
3
4
5
6
7
8
9
10
11
12
13
from django.core.mail import EmailMessage
import os
def send_mail_attachment(subject,body,to=[],attach_file=None):
    try:
        email = EmailMessage(subject,body,from_email=DEFAULT_FROM_EMAIL,to=to)
        attach_file =  os.path.join(BASE_DIR,attach_file)
        email.attach_file(attach_file)
        email.send(fail_silently=False)
        print('send mail success,mail attachment:{}'.format(attach_file))
        return True
    except Exception as e:
        logger.exception(e)
        return False

pure python,这里介绍通过thread发送,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class EmailThreadPure(threading.Thread):
    def __init__(self, subject, message, fail_silently=False, html_message=None):
        self.smtpserver = EMAIL_HOST
        self.user = EMAIL_HOST_USER
        self.sender = EMAIL_HOST_USER
        self.receiver = [a[1] for a in settings.ADMINS]
        self.body = message
        self.subject = subject
        self.html_message = html_message
        threading.Thread.__init__(self)
    
    def attach(self):
      """if attachment,use it"""
        ctype, encoding = mimetypes.guess_type(self.attach_file)
        if ctype is None or encoding is not None:
            ctype = "application/octet-stream"
        maintype, subtype = ctype.split("/", 1)
        if maintype == "text":
            with open(self.attach_file, "rb") as fp:
                # Note: we should handle calculating the charset
                attachment = MIMEText(fp.read(), _subtype=subtype)
        elif maintype == "image":
            with open(self.attach_file, "rb") as fp:
                attachment = MIMEImage(fp.read(), _subtype=subtype)
        elif maintype == "audio":
            with open(self.attach_file, "rb") as fp:
                attachment = MIMEAudio(fp.read(), _subtype=subtype)
        else:
            with open(self.attach_file, "rb") as fp:
                attachment = MIMEBase(maintype, subtype)
                attachment.set_payload(fp.read())
            encoders.encode_base64(attachment)
        encoders.encode_base64(attachment)
        attachment.add_header("Content-Disposition", "attachment", filename=self.attach_file)
        self.msg.attach(attachment)

    def run(self):
        # use html and plain mode,if no html,just self.msg = MIMEMultipart()
        self.msg = MIMEMultipart('alternative')
        self.msg['Subject'] = Header('%s%s' % (settings.EMAIL_SUBJECT_PREFIX, self.subject), 'utf-8')
        self.msg['from'] = EMAIL_HOST_USER
        # msg[to] is a string
        self.msg['to'] = ', '.join(self.receiver)
        self.msg.attach(MIMEText(self.body, 'plain', 'utf-8'))
        self.msg.attach(MIMEText(self.html_message, 'html', 'utf-8'))
        server = smtplib.SMTP(self.smtpserver, EMAIL_PORT, timeout=30)
        #debug mode
        # server.set_debuglevel(1)
        
        # USE TLS
        server.ehlo()
        server.starttls()
        
        # if need login
        server.login(EMAIL_HOST_USER,EMAIL_HOST_PASSWORD)
        #self.receiver is a list
        server.sendmail(self.sender, self.receiver, self.msg.as_string())
        server.quit()

列坑

  • DEFAULT_FROM_EMAIL与send mail中的from_email不一致。

    error info

    SMTPDataError: (554, ‘5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied; Failed to process message due to a permanent exception with message Cannot submit message.

    fix:统一DEFAULT_FROM_EMAIL与send mail中的from_email

    应该使用tsl时,未设置tsl。

  • error info:AUTH extension not supported by server

    fix:设置tsl,settings设置或者

    1
    2
    
    mail.ehlo() 
    mail.starttls()
    


  • python pure发邮件时,当多个收件人,可能只有第一个收件人能收到。

    error info:无,但会发现只有第一个人收到。

    fix:因为self.receiver为str的时候,会自动截取第一个。

    ​ self.msg[‘to’] = ‘, ‘.join(self.receiver)

    ​ server.sendmail(self.sender, self.receiver, self.msg.as_string())

    ​ self.receiver为list

  • 如果settings配置错误,很有可能是不会报错并且返回发送邮件数量也为1,但实际上是收不到邮件,检查端口号及tsl和ssl 配置。我碰到的情况时logger中设置的mail setting与Django setting backend不一样,加上公司邮箱设置问题,导致mail setting 里的邮件发不出来。

  • 未设置timeout时间,导致一直在connect mail server,无log,会误认为已经发送好了。 error info: 无,退出时会出现connect timeout,否则会一直连接。 fix:设置timeout时间。 可以使用telnet server port方式看是否连接有问题。

  • 避开敏感词汇,否则也可能会报554错误,如test等。