书接上回,上篇中主要介绍了电子邮件协议格式等理论相关内容,这篇中我们从JavaMail、Python的smtplib以及Django的django.core.mail模块三个视角来实现发送邮件这件事。
由于使用JavaMail API实现的代码与上篇中SMTP邮件源码结构最为相似,我们先从JavaMail API的代码看起。
package com.ys.mail;
import java.io.ObjectInputStream.GetField;
import java.util.Date;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.swing.text.html.MinimalHTMLWriter;
public class SendMailText {
//发件人地址 From
public static String senderAddress = "xxx@163.com";
//收件人地址 To
public static String recipientAddress = "xxx@qq.com";
//发件人账户名 API key信息
public static String senderAccount = "xxx";
//发件人账户密码 API key信息
public static String senderPassword = "xxx";
public static void main(String[] args) throws Exception {
//1、连接邮件服务器的参数配置
Properties props = new Properties();
//设置用户的认证方式
props.setProperty("mail.smtp.auth", "true");
//设置传输协议
props.setProperty("mail.transport.protocol", "smtp");
//设置发件人的SMTP服务器地址
props.setProperty("mail.smtp.host", "smtp.163.com");
//2、创建定义整个应用程序所需的环境信息的 Session 对象
Session session = Session.getInstance(props);
//设置调试信息在控制台打印出来
session.setDebug(true);
//3、创建邮件的实例对象
Message msg = getMimeMessage(session);
//4、根据session对象获取邮件传输对象Transport
Transport transport = session.getTransport();
//设置发件人的账户名和密码
transport.connect(senderAccount, senderPassword);
//发送邮件,并发送到所有收件人地址,message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
transport.sendMessage(msg,msg.getAllRecipients());
//如果只想发送给指定的人,可以如下写法
//transport.sendMessage(msg, new Address[]{new InternetAddress("xxx@qq.com")});
//5、关闭邮件连接
transport.close();
}
/**
* 获得创建一封邮件的实例对象
* @param session
* @return
* @throws MessagingException
* @throws AddressException
*/
public static MimeMessage getMimeMessage(Session session) throws Exception{
//创建一封邮件的实例对象
MimeMessage msg = new MimeMessage(session);
//设置发件人地址
msg.setFrom(new InternetAddress(senderAddress));
/**
* 设置收件人地址(可以增加多个收件人、抄送、密送),即下面这一行代码书写多行
* MimeMessage.RecipientType.TO:发送
* MimeMessage.RecipientType.CC:抄送
* MimeMessage.RecipientType.BCC:密送
*/
msg.setRecipient(MimeMessage.RecipientType.TO,new InternetAddress(recipientAddress));
//设置邮件主题
msg.setSubject("邮件主题","UTF-8");
//设置邮件正文
msg.setContent("简单的纯文本邮件!", "text/html;charset=UTF-8");
//设置邮件的发送时间,默认立即发送
msg.setSentDate(new Date());
return msg;
}
}
使用JavaMail API 需要围绕四个核心类编写,按照代码顺序,在指定好SMTP邮件头以及API KEY相关字段后,首先是Session类,用来定义整个应用程序所需的环境信息以及连接邮件服务器的参数配置信息。Message类用于创建和解析邮件实例对象。根据session对象获取邮件传输对象Transport,Transport负责建立连接并发送到对应的收件人地址,即将Message对象通过Transport对象的发送API(.sendMessage方法)发送到指定的SMTP服务器。
另外一个是Store类,它与Transport类正相反,假设使用POP3接收邮件,那么客户端接收邮件时,使用接收API获取到Store 对象,然后调用 Store 对象的接收方法,就可以从指定的 POP3 服务器获得邮件数据,并把这些邮件数据封装到表示邮件的 Message 对象中。
在Python中,smtplib库提供了方便的途径发送电子邮件。使用方法也是结合SMTP邮件结构,先构建出邮件头信息,然后使用sendmail方法发送邮件。
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])
简单示例如下:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
sender = 'test@qq.com'
receivers = ['test1@qq.com', 'test2@qq.com']
# MIMEText参数中:第一个为邮件文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码
message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8')
message['From'] = Header('python smtplib测试', 'utf-8')
message['To'] = Header('测试', 'utf-8')
subject = '邮件测试 smtp subject'
message['Subject'] = Header(subject, 'utf-8')
try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, message.as_string())
print('邮件发送成功')
except smtplib.SMTPException:
print('Error: 无法发送邮件')
在实际工作中我们一般会通过第三方的SMTP服务来实现发送,这里以腾讯QQ邮箱为例,首先需要为自己的账户生成授权码做为邮箱账户密码;QQ 邮箱 SMTP 服务器地址:smtp.exmail.qq.com,ssl 端口:465。
import smtplib
from email.mime.text import MIMEText
from email.header import Header
# 使用QQ的SMTP服务
mail_host = "smtp.exmail.qq.com" # 设置服务器
mail_user = "XXX" # 账户名
mail_pass = "******" # 授权码口令
sender = 'test@qq.com'
receivers = ['123456@qq.com', 'test@qq.com']
message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8')
message['From'] = Header('python smtplib测试', 'utf-8')
message['To'] = Header('测试', 'utf-8')
subject = '邮件测试 smtp subject'
message['Subject'] = Header(subject, 'utf-8')
try:
smtpObj = smtplib.SMTP()
smtpObj.connect(mail_host, 25)
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print('邮件发送成功')
except smtplib.SMTPException:
print('Error: 无法发送邮件')
Django 在Python smtplib的基础上提供了更简化的封装,发送邮件速度快,能在开发中自测,及在不支持 SMTP 的平台上支持发送邮件。代码位于 django.core.mail 模块。
邮件是通过SMTP主机端口发送的,需要将对应邮件后端配置加到项目的settings.py中,这里的AIL_HOST_PASSWORD就是账户的授权码,除了以下字段,DEFAULT_CHARSET指定了you邮件字符编码,默认是'utf-8'。
EMALL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.exmail.qq.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = 'test@qq.com'
EMAIL_HOST_PASSWORD = 'ALA3DaV3Hb3xjp5R'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
from django.shortcuts import render, HttpResponse, HttpResponseRedirect
from django.core.mail import send_mail, send_mass_mail, BadHeaderError
# Create your views here.
def send_email(request):
"""
发送邮件
:param request:
:return:
"""
res = send_mail(subject='这是一封测试邮件',
message='这里是测试邮件的内容',
from_email='test@qq.com',
recipient_list=['123456@qq.com', 'test@qq.com'],
fail_silently=False
)
return HttpResponse('邮件发送成功')
def send_mass_email(request):
"""
批量发送邮件
:param request:
:return:
"""
message1 = ('这是一封测试邮件', '这里是测试邮件的内容',
'test@qq.com', ['123456@qq.com', 'test@qq.com'])
message2 = ('这是另外一封测试邮件', '这里是另外一封测试邮件的内容',
'test@qq.com', ['123456@qq.com', 'test@qq.com'])
send_mass_mail((message1, message2), fail_silently=False)
return HttpResponse('另外一封邮件发送成功')
def send_mail_ex(request):
"""
测试头注入异常
:param request:
:return:
"""
subject = request.POST.get('subject', '')
message = request.POST.get('message', '')
from_email = request.POST.get('from_email', '')
if subject and message and from_email:
try:
send_mail(subject, message, from_email, ['admin@example.com'])
except BadHeaderError:
return HttpResponse('Invalid header found.')
return HttpResponseRedirect('/contact/thanks/')
else:
return HttpResponse('Make sure all fields are entered and valid.')
发送一封邮件使用 django.core.mail.send_mail() 来发送;fail_silently字段是一个布尔值,若为 False,send_mail() 会在发生错误时抛出smtplib.SMTPException异常。
批量发送邮件使用django.core.mail.send_mass_mail();第一个参数datatuple是一个元组,它为其中每一个元素生成一份独立的邮件内容,如同示例代码中的message1, message2。
send_mail()和send_mass_mail()方法的返回值都是成功发送的消息的数量。两者区别在于send_mail()每次执行需要重新建立连接,而send_mass_mail()使用一个链接批量发送邮件,效率相对较高。
邮件头注入是一个开发漏洞,攻击者可以利用在邮件头插入脚本,控制收件人和发件人内容。Django包含了反头注入功能,以send_mail(subject, message, from_email, recipient_list)函数为例,如果在subject, message, from_email或recipient_list包含了新行,邮件函数会中断发送抛出BadHeaderError异常。在示例代码中从请求的 POST 数据中获取 subject, message 和 from_email,并将其发送至 admin@example.com ,成功后再重定向至 "/contact/thanks/"。
send_mail()和send_mass_mail()方法是对类EmailMessage的简单封装利用,如果你的需求是开发带附件的邮件、密送收件人、分段邮件等功能,需要直接创建EmailMessage的实例。EmailMessage的参数如下:
对于EmailMessage的实例发送一封邮件使用send()方法,多封使用send_messages() ,其原理也是复用同一条连接,在发送完毕后关闭连接。
from django.core import mail
connection = mail.get_connection() # Use default email connection
messages = get_notification_email()
connection.send_messages(messages)
我们在一开始提到Django中的邮件服务支持在开发中自测,开发中你不希望每次运行测试都发送邮件,Django的测试运行器将这些邮件重定向到虚拟发件箱,即通过将正常的邮件后端替换为测试后端实现,用到模块django.core.mail.outbox,但outbox是特殊属性无法直接导入,它的作用是存储所有已经发送的 EmailMessage 实例的列表。以下为检查outbox长度和内容示例:
from django.test import TestCase
from django.core import mail
# Create your tests here.
class EmailTest(TestCase):
def test_send_email(self):
# TestCase会为你执行mail.outbox = []
# Send message.
mail.send_mail(
'Subject here', 'Here is the message.',
'test@qq.com', ['1234567@qq.com'],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, 'Subject here')
参考:
https://www.cnblogs.com/ysocean/p/7666061.html
https://www.runoob.com/python/python-email.html
https://docs.djangoproject.com/zh-hans/3.2/topics/email/#topics-sending-multiple-emails
END