MENU

通过SMTP协议发送邮件

April 4, 2020 • Python

前言:

本文主要讲解的是如何通过Python自带的email库发送邮件

包括 纯文本文件的发送以及带有附件的邮件发送

这里我们使用的邮箱是网易的163邮箱,其他邮箱的操作类似,请自己测试

关于SMTP的介绍请看这里

开启smtp的教程在这儿,我们不再过多赘述,请牢记您的授权码

发送一个简单的纯文本邮件

在Python中,内置了对smtp协议的支持,内置了smtplib和email两个模块,其中,email负责邮件的额构造,smtplib负责发送邮件

这里我们先引入所需要的头文件

import smtplib
#负责发送邮件
from email import encoders
#负责邮件转码
from email.header import Header
#负责构造邮件的头部信息
from email.mime.text import MIMEText
#负责构造邮件的正文,在发送附件时引入的头文件有变化,请注意
from email.utils import parseaddr, formataddr
#用来格式化一个邮件地址的

现在我们开始来构造邮件的正文

def _format_addr(s):
    name,addr=parseadr()
    return formataddr(Header(name, 'utf-8').encode(), addr)
    #构造这个函数的主要作用是用来格式化邮件地址的

from_addr = input('From: ')
password = input('Password: ')
#注意这里的password指的是邮箱的授权码而非邮箱的登录密码!!!
to_addr = input('To: ')
smtp_server = 'smtp.163.com'
#smtp服务器的地址,163邮箱的默认地址是smtp.163.com
#其他邮箱请自己查阅smtp相关说明

msg = MIMEText('这里是待发送的文本', 'plain', 'utf-8')
#这里的plain为必填项,默认填上去就好了,填成text之类的会有各种毛病
msg['From'] = _format_addr('MrSen<%s>' % from_addr)
#这里显示发送来源
msg['To'] = _format_addr('Visitor<%s>' % to_addr)
#这里显示接收者名字
msg['Subject'] = Header('天生的偏执', 'utf-8').encode()
#这里是主题

然后准备发送邮件

server = smtplib.SMTP(smtp_server, 25)
#这里是通过25端口发送的,25端口是明码,安全性不高
#所以大部分的服务器都不允许通过25端口发送
#在服务上使用时,需要使用ssl协议
#server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
#发送到 可以传入多个收件人,所以这里使用的是列表的形式
server.quit()
#发送完毕退出

完整代码在这里

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 this is mrsen', 'plain', 'utf-8')
msg['From'] = _format_addr('mrsen<%s>' % from_addr)
msg['To'] = _format_addr('visitor<%s>' % to_addr)
msg['Subject'] = Header('欢迎光临我的博客^_^', '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()

这样,我们就发送了一个邮件

83iKJA.png

这里说一下,通过163邮箱发送给qq邮箱的时候可能会有报错:554, b'DT:SPM ,这个并非是代码本身的问题,而是qq邮箱自带的问题,导致发生了企业退信,换个邮箱发信息就好了

发送一个带有附件的邮件

发送带有附件的邮件的时候,需要有些许改变

首先是头文件

from email import encoders
#用于解码
from email.header import Header
#用于构造邮件头文件
from email.mime.text import MIMEText
#用于构造正文部分
from email.mime.multipart import MIMEMultipart
#用于构造带附件的邮件
from email.mime.base import MIMEBase
#用于文件编码
from email.utils import parseaddr,formataddr
#用于格式化邮件地址

import smtplib
import os

我们可以把带附件的邮件看成若干部分的邮件:文本和各个邮件本身,所以可以通过构造一个MIMEMultipart对象来表示邮件本身,再向邮件本身添加MIMEText来作为邮件正文,再继续向里面添加附件的MIMEBase对象即可

开始构造邮件对象:

msg = MIMEMultipart()
msg['From'] = _format_addr('MrSen<%s>' % from_addr)
msg['To'] = _format_addr('Vistor<%s>' % to_addr)
msg['Subject'] = Header('欢迎光临我的博客^_^', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('这是一个带有附件的邮件', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个文件:
file_path=os.getcwd()+"\\news.txt"
#这里我们是发送该程序所在的目录下的news.txt文件
with open(file_path, 'r') as f:
    mime = MIMEBase('application', 'octet-stream', filename='news.txt')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='news.txt')
    # mime.add_header('Content-ID', '<0>')
    # mime.add_header('X-Attachment-Id', '0')
    #发送二进制文件的时候记得把这里的注释取消
    #这里因为是发送的txt文件,所以需要注释上
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)

接下来就是发送了

和前面发送普通文本邮件一样

server = smtplib.SMTP(smtp_server, 25)
#通过25端口发送
# server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

完整代码:

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

import smtplib
import os

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

from_addr='在这里输入发件人地址'
password='在这里输入授权码'
to_addr='在这里输入收件人地址'
smtp_server='smtp.163.com'

# msg=MIMEText('hello,send by Mrsen','plain','utf-8')
msg=MIMEMultipart()
msg['From']=_format_addr('cheng<%s>' % from_addr)
msg['To']=_format_addr('chen<%s>' % to_addr)
msg['Subject']=Header('aloha','utf-8').encode()

msg.attach(MIMEText('hello,send by Mrsen','plain','utf-8'))

path=os.getcwd()+'today.txt'
with open(path,'rb') as f:
    mime=MIMEBase('application','octet-stream',filename='today.txt')
    mime.add_header('Content-Disposition', 'attachment', filename=('utf-8','','today.txt'))
    # mime.add_header('Content-ID', '<0>')
    # mime.add_header('X-Attachment-Id', '0')
    mime.set_payload(f.read())
    encoders.encode_base64(mime)
    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()

但是,因为使用的是25端口,始终无法再服务器上正常运行,因此,我们需要使用ssl协议将邮件加密发送。使用ssl协议的时候,我们就需要使用邮箱服务商提供的其他端口了,比如163邮箱提供的就是465端口,我们将代码发送部分稍作修改

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

这样,就能通过465端口发送ssl协议的邮件了

既然发邮件这么复杂,那有没有简单一点的方法呢?

当然有!博主将前面的代码封装成了一个类,使用时将这个程序复制到需要发送邮件的程序的同目录下即可

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

class send_email_with_attachment():
    def __init__(self,postinfo):
        self.from_addr=postinfo.get('from_addr')
        self.password=postinfo.get('password')
        self.to_addr=postinfo.get('to_addr')
        self.smtp_server=postinfo.get('smtp_server','smtp.163.com')
        #default server is Netease mail

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

    def make_mail(self,Subject,text,path):
        self.msg=MIMEMultipart()
        self.msg['From']=self._format_addr('<%s>' % self.from_addr)
        self.msg['To']=self._format_addr('<%s>' % self.to_addr)
        self.msg['Subject']=Header(Subject,'utf-8').encode()
        self.msg.attach(MIMEText(text,'plain','utf-8'))
        filename=path.split('\\')[-1]
        with open(path,'rb') as f:
            mime=MIMEBase('application','octet-stream',filename=filename)
            mime.add_header('Content-Disposition', 'attachment', filename=filename)
            # mime.add_header('Content-ID', '<0>')
            # mime.add_header('X-Attachment-Id', '0')
            mime.set_payload(f.read())
            encoders.encode_base64(mime)
            self.msg.attach(mime)

    def send_email(self):
        server = smtplib.SMTP_SSL(self.smtp_server, 465)
        # server.starttls()
        # server.set_debuglevel(1)
        server.login(self.from_addr, self.password)
        server.sendmail(self.from_addr, [self.to_addr], self.msg.as_string())
        server.quit()

class send_email_only_txt():
    def __init__(self,postinfo):
        self.from_addr=postinfo.get('from_addr')
        self.password=postinfo.get('password')
        self.to_addr=postinfo.get('to_addr')
        self.smtp_server=postinfo.get('smtp_server','smtp.163.com')
        #default server is Netease mail

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

    def make_mail(self,Subject,text):
        self.msg = MIMEText(text, 'plain', 'utf-8')
        self.msg['From'] = self._format_addr('<%s>' % self.from_addr)
        self.msg['To'] = self._format_addr('<%s>' % self.to_addr)
        self.msg['Subject'] = Header(Subject, 'utf-8').encode()
    def send_email(self):
        server = smtplib.SMTP_SSL(self.smtp_server, 465)
        server.login(self.from_addr, self.password)
        server.sendmail(self.from_addr, [self.to_addr], self.msg.as_string())
        server.quit()
if __name__=="__main__":
    dic={
        'from_addr':'发件人地址',
        'password':'邮箱授权码',
        'to_addr':'收件人地址'
    }
    mail=send_email_with_attachment(dic)
    Subject='hey man,how are you!!'
    text='we have not say each other for a long time'
    location=os.getcwd()+"\\push\\"+'pic.gif'
    mail.make_mail(Subject,text,location)
    mail.send_email()

使用方法:

首先需要构造一个字典,其中必须包括三个键:from_addr,password,to_addr,另外有smtp_server可选

他们分别表示了发件人地址,邮箱授权码,收件人地址,以及邮箱smtp服务器地址,使用时,先实例化一个类

  1. mail=send_email_with_attachment()表示发送带有附件的邮箱,在向make_email方法中传入参数时,需要传入subject(主题)text(正文)以及location(附件的绝对地址)三个参数,缺一不可
  2. mail=send_email_only_text()表示发送仅有正文的邮箱,在向make_email方法中传入参数时,需要传入subject(主题)text(正文)两个参数,缺一不可

然后使用send_mail方法发送邮件,看看你的邮箱里有没有收到邮件吧!

模板github地址:https://github.com/NorthCityChen/email_assistant

本文参考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017790702398272

作者:NorthCity1984
出处:https://grimoire.cn/python/smtp.html
版权:本文《通过SMTP协议发送邮件》版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任