Python发送邮件的贯彻

Python基于smtplib实现异步发送邮件服务,pythonsmtplib

基于smtplib包制作而成,但在实践中发现一个不知道算不算是smtplib留的一个坑,在网络断开的情况下发送邮件时会抛出一个socket.gaierror的异常,但是smtplib中并没有捕获这个异常,导致程序会因这个异常终止,因此代码中针对这部分的异常进行处理,确保不会异常终止。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

__author__ = 'Zoa Chou'
# see http://www.mudoom.com/Article/show/id/29.html for detail

import logging
import smtplib
import mimetypes
import socket
from email import encoders
from email.header import Header
from email.mime.text import MIMEText, MIMENonMultipart
from email.mime.base import MIMEBase
from email.utils import parseaddr, formataddr


class Mailer(object):
  def __init__(self):
    pass

  def send_mail(self, smtp_server, from_address, to_address, subject, body, files=None):
    """
    发送邮件主程序
    :param smtp_server: dict 邮件服务器设置
      :keyword host: string smtp服务器地址
      :keyword port: int smtp服务器端口号
      :keyword user: string 用户名
      :keyword passwd: string 密码
      :keyword ssl: bool 是否启用ssl,默认False
      :keyword timeout: int 超时时间,默认10s
    :param from_address: 发件人邮箱
    :param to_address: 收件人邮箱
    :param subject: 邮件标题
    :param body: 邮件内容
    :param files: 附件
    :raise: NetworkError/MailerException
    """
    # 格式化邮件内容
    body = self._encode_utf8(body)
    # 邮件类型
    content_type = 'html' if body.startswith('<html>') else 'plain'
    msg = MIMENonMultipart() if files else MIMEText(body, content_type, 'utf-8')
    # 格式化邮件数据
    msg['From'] = self._format_address(from_address)
    msg['To'] = ', '.join(self._format_list(to_address))
    msg['subject'] = self._encode_utf8(subject)

    # 构造附件数据
    if files:
      msg.attach(MIMEText(body, content_type, 'utf-8'))
      cid = 0
      for file_name, payload in files:
        file_name = self._encode_utf8(file_name)
        main_type, sub_type = self._get_file_type(file_name)
        if hasattr(payload, 'read'):
          payload = payload.read()
        f_name = self._encode_header(file_name)
        mime = MIMEBase(main_type, sub_type, filename=f_name)
        mime.add_header('Content-Disposition', 'attachment', filename=f_name)
        mime.add_header('Content-ID', '<%s>' % cid)
        mime.add_header('X-Attachment-Id', '%s' % cid)
        mime.set_payload(payload)
        encoders.encode_base64(mime)
        msg.attach(mime)
        cid += 1

    host = smtp_server.get('host')
    port = smtp_server.get('port')
    user = smtp_server.get('user')
    passwd = smtp_server.get('passwd')
    ssl = smtp_server.get('ssl', False)
    time_out = smtp_server.get('timeout', 10)

    # 没有输入端口则使用默认端口
    if port is None or port == 0:
      if ssl:
        port = 465
      else:
        port = 25

    logging.debug('Send mail form %s to %s' % (msg['From'], msg['To']))

    try:
      if ssl:
        # 开启ssl连接模式
        server = smtplib.SMTP_SSL('%s:%d' % (host, port), timeout=time_out)
      else:
        server = smtplib.SMTP('%s:%d' % (host, port), timeout=time_out)
      # 开启调试模式
      # server.set_debuglevel(1)

      # 如果存在用户名密码则尝试登录
      if user and passwd:
        server.login(user, passwd)

      # 发送邮件
      server.sendmail(from_address, to_address, msg.as_string())

      logging.debug('Mail sent success.')

      # 关闭stmp连接
      server.quit()

    except socket.gaierror, e:
      """ 网络无法连接 """
      logging.exception(e)
      raise NetworkError(e)

    except smtplib.SMTPServerDisconnected, e:
      """ 网络连接异常 """
      logging.exception(e)
      raise NetworkError(e)

    except smtplib.SMTPException, e:
      """ 邮件发送异常 """
      logging.exception(e)
      raise MailerException(e)

  def _format_address(self, s):
    """
    格式化邮件地址
    :param s:string 邮件地址
    :return: string 格式化后的邮件地址
    """
    name, address = parseaddr(s)
    return formataddr((self._encode_header(name), self._encode_utf8(address)))

  def _encode_header(self, s):
    """
    格式化符合MIME的头部数据
    :param s: string 待格式化数据
    :return: 格式化后的数据
    """
    return Header(s, 'utf-8').encode()

  def _encode_utf8(self, s):
    """
    格式化成utf-8编码
    :param s: string 待格式化数据
    :return: string 格式化后的数据
    """
    if isinstance(s, unicode):
      return s.encode('utf-8')
    else:
      return s

  def _get_file_type(self, file_name):
    """
    获取附件类型
    :param file_name: 附件文件名
    :return: dict 附件MIME
    """
    s = file_name.lower()
    pos = s.rfind('.')
    if pos == -1:
      return 'application', 'octet-stream'

    ext = s[pos:]
    mime = mimetypes.types_map.get(ext, 'application/octet-stream')
    pos = mime.find('/')
    if pos == (-1):
      return mime, ''
    return mime[:pos], mime[pos+1:]

  def _format_list(self, address):
    """
    将收件人地址格式化成list
    :param address: string/list 收件人邮箱
    :return: list 收件人邮箱list
    """
    l = address
    if isinstance(l, basestring):
      l = [l]
    return [self._format_address(s) for s in l]


class MailerException(Exception):
  """ 邮件发送异常类 """
  pass


class NetworkError(MailerException):
  """ 网络异常类 """
  pass

# test for @qq.com
if __name__ == '__main__':
  import sys

  def prompt(prompt):
    """
    接收终端输入的数据
    """
    sys.stdout.write(prompt + ": ")
    return sys.stdin.readline().strip()

  from_address = prompt("From(Only @qq.com)")
  passwd = prompt("Password")
  to_address = prompt("To").split(',')
  subject = prompt("Subject")
  print "Enter message, end with ^D:"
  msg = ''
  while 1:
    line = sys.stdin.readline()
    if not line:
      break
    msg = msg + line
  print "Message length is %d" % len(msg)
  # QQ邮箱默认设置
  smtp_server = {'host': 'smtp.qq.com', 'port': None, 'user': from_address, 'passwd': passwd, 'ssl': True}
  mailer = Mailer()

  try:
    mailer.send_mail(smtp_server, from_address, to_address, subject, msg)
  except MailerException, e:
    print(e)

以上所述就是本文的全部内容了,希望大家能够喜欢。

基于smtplib包制作而成,但在实践中发现一个不知道算不算是smtplib留的一个坑,在网络断…

原理

网上已经有了很多的教程讲解相关的发送邮件的原理,在这里还是推荐一下廖雪峰老师的Python教程,讲解通俗易懂。简要来说,SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。而python内置的email模块则是负责邮件的内容、发送方、接收方等内容;具体的操作可以看代码。

而构造一个邮件对象就是一个Messag对象,如果构造一个MIMEText对象,就表示一个文本邮件对象,如果构造一个MIMEImage对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart对象,而MIMEBase可以表示任何对象。他们的嵌套关系如下:

Message
+- MIMEBase
   +- MIMEMultipart
   +- MIMENonMultipart
      +- MIMEMessage
      +- MIMEText
      +- MIMEImage

今天学到了如何使用Python的smtplib库发送邮件,中间也是遇到了各种各样的错误和困难,还好都一一的解决了。下面来谈一谈我的这段经历。

# coding:utf-8

在我们的工作中,会有诸如这种需求:

一些错误的总结

[1]提示smtplib.SMTPAuthenticationError: (550, b’User has no
permission’)
这是因为邮箱没有开启客户端授权,邮箱这边的SMTP服务运行不起来;而现在基本所有的邮件都是需要客户端授权的,这里需要注意一下。解决办法为:进入163邮箱-设置-客户端授权密码-开启(授权码是用于登录第三方邮件客户端的专用密码),非第三方登录密码不变。
[2]提示smtplib.SMTPAuthenticationError: (535, b’Error: authentication
failed’)
 以163邮箱为例,在开启POP3/SMTP服务,并开启客户端授权密码时会设置授权码,将这个授权码代替smtplib.SMTP().login(user,password)方法中的password即可。就是说你代码中的password为你所设置的授权码。
[3]提示554
说明邮件内容缺少信息,在现在的邮箱里一般有些内容需要填写。


# __author__ = ‘Gao’

Q1:我的测试用例实现自动构建了,怎么在构建完让程序通知我结果?

代码

# 发送文本
# -*- coding: utf-8 -*-

from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

# 发的内容
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
# 发件人
msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
# 收件人
msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
# 标题
msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

# 发送附件
# -*- coding: utf-8 -*-

from email import encoders
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.utils import parseaddr, formataddr
import smtplib

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('e:/141.m4a', 'rb') as f:
    # 设置附件的MIME和文件名,这里是音乐类型:
    mime = MIMEBase('image', 'm4a', filename='141.m4a')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='141.m4a')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

代码经笔者都测试过,应该是没问题的。遇到错误看看是不是客户端的授权问题,或者是python的版本的问题。

配置你的邮箱

为什么要配置邮箱呢?具体要配置什么呢?

因为我们申请的一些免费邮箱都是默认不开启smtp/pop协议的。
SMTP是发邮件使用到的计算机网络中应用层协议中的一个;而POP则是收邮件时使用到的计算机网络中的应用层协议的其中一个。这都是理论性的知识了,上过计算机网络这门课的想必都知道,就不多说了。

配置就是要开启这项服务。否则我们就不能实现用Python代码来控制发送和接收邮件了。

我们在配置完成之后,系统会提示给我们一个邮箱客户端的授权码。作用就是我们登陆的时候替代原来的登陆密码。大家一定要完善保存咯。我这里采用的是163的邮箱,所以是可以自己设置这个授权码的。

import smtplib

Q2:我的监控程序部署完了,怎么能让我在出问题时及时得到消息?

使用代码发邮件

还是按照,先看代码再研究的策略。如下:

# coding:utf-8

import smtplib
from email.mime.text import MIMEText
from email.header import Header



"""
请确保自己的邮箱的smtp协议开启,都则会出现认证的错误的,如ssh等
"""


sender = "1564086XXXX@163.com"
# 收件人,可以是多个
receivers = ['102170XXXX@qq.com']

# 三个参数:第一个为纯文本,第二个plain设置文本格式,第三个为编码格式
message = MIMEText('这里是发送的邮件的主要的内容。Pure Text Here!','plain','utf-8')
message['From'] = Header('来自Mark','utf-8')
message['To'] = Header('测试标题','utf-8')

subject = '哈哈哈哈哈哈,这是邮件的主题 '
message['Subject'] = Header(subject,'utf-8')

try:
    smtpObj = smtplib.SMTP()
    smtpObj.connect('smtp.163.com',25)
    smtpObj.login(sender,'你的客户端授权码')
    smtpObj.sendmail(sender,receivers,message.as_string())
    smtpObj.quit()
    print '邮件已成功发送了'
except smtplib.SMTPException,e:
    print  e.message

测试的结果:

D:\Software\Python2\python.exe E:/Code/Python/MyTestSet/mail/PureText.py
邮件已成功发送了

Process finished with exit code 0

这里写图片描述

确实是成功发送了的。

# ============通过QQ发送普通文件邮件====================

以上的问题相信大家都有了答案:那就是通过邮件和短信。下面我们就来看看怎么用Python实现发送邮件,本人已经试验多次并一直在使用,源码附上:

代码探查

下面就来深入的研究一下发送邮件实现的流程吧。我们注意到了,最最主要的是下面的这样一段代码。

    smtpObj = smtplib.SMTP()
    smtpObj.connect('smtp.163.com',25)
    smtpObj.login(sender,'你的客户端授权码')
    smtpObj.sendmail(sender,receivers,\
    message.as_string()
    smtpObj.quit()

不难看出,主要是经历了下面的这些流程:

  • 获得SMTP服务
  • 连接服务器
  • 模拟客户端登陆
  • 实现发送邮件
  • 退出登录

其中核心的也就是客户端登陆和发送邮件的两个步骤。模拟登陆的时候需要注意的就是使用你自己的授权码就行了,没什么难度。而发送邮件的时候需要注意一下参数。发件人是一个,而收件人是一个列表,里面可以有很多个收件人(这样可以借助这个列表实现邮件的群发)。


其他需要注意的也就是使用MIME类型的数据,和使用utf-8编码就行了。属于非智力相关的内容。所以不用太在意。会用就可以了。

这样看来,发个邮件什么的也不是很难嘛。(\_\_) 嘻嘻……

# 1>清楚QQ邮件服务器的主机地址

#coding=utf-8

import smtplib

import string

from email.mime.text import MIMEText

import base64

import sys

reload(sys)

sys.setdefaultencoding( “utf-8” )

class Mailsender():

    def __init__(self):

        print “I am sending the mails…”

    def setSmtpServer(self, smtpServer):

        self.smtpserver = smtpServer

    def setSender(self, sender, password):

        self.sender = sender

        self.password = password

    def setReceiver(self, receiver):

        self.receiver = receiver

    def setSubject(self, subject):

        self.subject = subject

    def setContent(self, content):

        self.content = content

    def sendMail(self):

        smtp = smtplib.SMTP()

        smtp.connect(self.smtpserver, 25)

        smtp.login(self.sender, self.password)

        self.content = base64.encodestring(self.content)

        msg =
“From:%s\nTo:%s\nSubject:%s\nContent-Type:text/html;charset=UTF-8\nContent-Transfer-Encoding:base64\n\n%s”
% (self.sender, self.receiver, self.subject, self.content)

        smtp.sendmail(self.sender, self.receiver, msg)

        smtp.close()

    def __del__(self):

        print “Finish sending mails !”

# Main

if __name__ == “__main__”:

# 获取邮件主题

mailSubject = sys.argv[1]

# 获取邮件内容

mailContent = sys.argv[2]

# 获取收件人list

receiverList = sys.argv[3]

receiverList = string.splitfields(receiverList, “,”) #
收取邮件的邮箱地址,用逗号隔开

mail = Mailsender()

mail.setSmtpServer(“smtp.xxxx.qq.com”) # Smtp Server地址

mail.setSender(‘xxxxxxxxxxxx@qq.com’, “xxxxxx”)
#发送邮件邮箱的用户名和密码

mail.setReceiver(receiverList)

mail.setSubject(mailSubject)

mail.setContent(mailContent)

mail.sendMail()

错误总结

  • 错误一:500、530等5开头的错误,大致为什么ssh异常啊什么的。有点web常识的都知道,5开头的错误代码一般都是服务器内部的错误,所以这基本上可以判断出我们的代码其实没有出错。我们主要的排错方向应该是服务器端了(当然了,凡是没有任何的绝对,但这种情况出现的概率确实是很小的)。这时,我们要检查一下自己的邮箱的SMTP/POP服务开启了没有啊等等

  • 错误二
    :授权码没有写或者填写成了自己的邮箱之前的密码,错误提示一般都是认证相关的。这一点属于低级错误了。因为在配置完自己的SMTP/POP服务之后,系统会发还一个短信提示,告知用户要使用授权码替代原密码登陆邮箱。所以使用你的授权码进行登录呗。

  • 错误三:代码中的错误。这就更加的不应该了,少写了必填项,或者编码没添加等类似的错误是我们应该极力避免的,所以尽量保证自己的代码的正确。

HOST =’smtp.qq.com’

#QQ邮箱的端口是465

PORT =’465′

# 指定发件人

FROM =’发件人的邮箱’

# 指定收件人

TO =’收件人邮箱’

# 邮件标题

SUBJECT =’测试邮件’

# 邮件内容

TEXT =’这是一个测试邮件!是由<xxxxxxx@qq.com>发出的!’

# 2>创建邮件客户端对象

# smtplib.SMTP(): 传输过程不加密

smtp_obj = smtplib.SMTP_SSL()# 将传输内容加密,QQ强制要求的。

# 3>通过主机地址HOST以及端口号PORT与QQ邮箱服务器建立连接。

smtp_obj.connect(host=HOST,port=PORT)

# 4>登录邮箱服务器进行发件人的认证

#
用户就是发件人的邮箱,密码使用授权码!当前创建的邮箱客户端对象属于第三方客户端,要求使用授权码替代密码进行登录验证。

result = smtp_obj.login(user=FROM,password=’授权码’)

print ‘登录结果:’, result

# 5>发送邮件

# From: To: Subject: 这三个是发送邮件时,必传的三个参数,而且不能修改。

message_content
=’\n’.join([‘From:%s’%FROM,’To:%s’%TO,’Subject:%s’%SUBJECT,”, TEXT])

smtp_obj.sendmail(from_addr=FROM,to_addrs=[TO],msg=message_content)

# =====================通过163邮箱发送普通文本=====================

# 1>清楚QQ邮件服务器的主机地址

HOST =’smtp.163.com’

PORT =’25’

# 指定发件人

FROM =’17737713931@163.com’

# 指定收件人,如果是要发给多人的话,可以写在一个字符串中,以”逗号”隔开;

TO = ‘xbxxxxxx@qq.com,121223232@163.com’

# 邮件标题

SUBJECT =’测试邮件’

# 邮件内容

TEXT =’这是一个测试邮件!是由<17737713931@163.com>发出的!’

# 2>创建邮件客户端对象

# smtplib.SMTP(): 传输过程不加密

smtp_obj = smtplib.SMTP()

# 3>通过主机地址HOST以及端口号PORT与QQ邮箱服务器建立连接。

smtp_obj.connect(host=HOST,port=PORT)

# 4>登录邮箱服务器进行发件人的认证

#
用户就是发件人的邮箱,密码使用授权码!当前创建的邮箱客户端对象属于第三方客户端,要求使用授权码替代密码进行登录验证。

result = smtp_obj.login(user=FROM,password=’haha521′)

print ‘登录结果:’, result

# 5>发送邮件

# From: To: Subject: 这三个是发送邮件时,必传的三个参数,而且不能修改。

message_content
=’\n’.join([‘From:%s’%FROM,’To:%s’%TO,’Subject:%s’%SUBJECT,”, TEXT])

smtp_obj.sendmail(from_addr=FROM,to_addrs=TO,msg=message_content)

发表评论

电子邮件地址不会被公开。 必填项已用*标注