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


Posted in Python onMay 28, 2015

基于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)

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

Python 相关文章推荐
Python中的exec、eval使用实例
Sep 23 Python
把MySQL表结构映射为Python中的对象的教程
Apr 07 Python
简单说明Python中的装饰器的用法
Apr 24 Python
Python发送邮件功能示例【使用QQ邮箱】
Dec 04 Python
对Python发送带header的http请求方法详解
Jan 02 Python
matplotlib实现区域颜色填充
Mar 18 Python
python打开使用的方法
Sep 30 Python
python代码打印100-999之间的回文数示例
Nov 24 Python
使用Pandas的Series方法绘制图像教程
Dec 04 Python
Python实现非正太分布的异常值检测方式
Dec 09 Python
什么是python的函数体
Jun 19 Python
Python confluent kafka客户端配置kerberos认证流程详解
Oct 12 Python
Python使用Scrapy爬取妹子图
May 28 #Python
Python实现统计单词出现的个数
May 28 #Python
Python下载懒人图库JavaScript特效
May 28 #Python
Python实现给qq邮箱发送邮件的方法
May 28 #Python
Python import用法以及与from...import的区别
May 28 #Python
Python中使用不同编码读写txt文件详解
May 28 #Python
Python实现统计英文单词个数及字符串分割代码
May 28 #Python
You might like
星际初学者游戏中永远要做的事
2020/03/04 星际争霸
PHP开启gzip页面压缩实例代码
2010/03/11 PHP
使用php的HTTP请求的库Requests实现美女图片墙
2015/02/22 PHP
php打包压缩文件之ZipArchive方法用法分析
2016/04/30 PHP
php实现博客,论坛图片防盗链的方法
2016/10/15 PHP
javascript下function声明一些小结
2007/12/28 Javascript
Ext.get() 和 Ext.query()组合使用实现最灵活的取元素方式
2011/09/26 Javascript
javascript学习笔记(十四) window对象使用介绍
2012/06/20 Javascript
JS获取当前网页大小以及屏幕分辨率等
2014/09/05 Javascript
GitHub上一些实用的JavaScript的文件压缩解压缩库推荐
2016/03/13 Javascript
Bootstrap优化站点资源、响应式图片、传送带使用详解3
2016/10/14 Javascript
初探nodeJS
2017/01/24 NodeJs
NodeJS配置HTTPS服务实例分享
2017/02/19 NodeJs
NodeJS收发GET和POST请求的示例代码
2017/08/25 NodeJs
浅谈Vue 初始化性能优化
2017/08/31 Javascript
vue+php实现的微博留言功能示例
2019/03/16 Javascript
微信小程序自定义波浪组件使用方法详解
2019/09/21 Javascript
微信小程序轮播图swiper代码详解
2020/12/01 Javascript
Python 实现文件的全备份和差异备份详解
2016/12/27 Python
python 调用c语言函数的方法
2017/09/29 Python
python issubclass 和 isinstance函数
2019/07/25 Python
利用python实现短信和电话提醒功能的例子
2019/08/08 Python
将 Ubuntu 16 和 18 上的 python 升级到最新 python3.8 的方法教程
2020/03/11 Python
Pycharm github配置实现过程图解
2020/10/13 Python
5款实用的python 工具推荐
2020/10/13 Python
基于HTML5的齿轮动画特效
2016/02/29 HTML / CSS
基于html5绘制圆形多角图案
2016/04/21 HTML / CSS
MyBag中文网:英国著名的时尚包袋电商零售网站
2020/07/31 全球购物
出国签证在职证明
2014/09/20 职场文书
2014基层党员批评与自我批评范文
2014/09/24 职场文书
自查自纠工作情况报告
2014/10/29 职场文书
旷课检讨书范文
2014/10/30 职场文书
2014年乡镇个人工作总结
2014/12/03 职场文书
保洁员岗位职责
2015/02/04 职场文书
详解Oracle块修改跟踪功能
2021/11/07 Oracle
Python学习之异常中的finally使用详解
2022/03/16 Python