通过阿里云服务发送邮件
1. 整体描述
项目有发送邮件的需求,主要就对阿里云服务器发送邮件的功能调研了一下
2. 方案选择
阿里云提供三种发送邮件的方式,分别为: 控制台发送,API接口发送和SMTP方式发送。
2.1 控制台发送
无需开发,在控制台配置发送,目前支持发送批量邮件,暂不支持触发邮件。
2.2 API接口接入
编写程序,调用邮件推送产品的 API 接口,传输邮件数据。请求成功后,邮件推送对邮件数据进行处理和投递。通过接口可以发送触发邮件和批量邮件。不支持添加附件。
2.3 SMTP接口接入
编写程序,调用标准的 SMTP 接口,传输邮件数据。通过 SMTP 接口可以发送触发邮件和批量邮件。
优点:标准化的邮件发送协议,轻松接入无需额外的接口适配。
2.4 结论
暂定使用SMTP方式,阿里云官方建议使用此方式,开发相对简单。
3. 前期工作
3.1 准备工作
- 申请阿里云账号,在控制台中搜索【邮箱推送】控制台,开通并配置邮箱服务。
- 申请域名,最好在阿里云上申请,此域名为发送邮件的域名地址。域名申请会有一定费用,具体和域名有关,.com的域名收费较高。在阿里云搜索【域名】控制台,可以购买域名。
3.2 配置工作
- 在阿里云【邮件推送】控制台,增加发信域名,填写上面申请的域名
- 登录域名注册商网站,将查询的域名配置记录值逐条添加域名解析,使用阿里云网站,直接可以在阿里云【云解析DNS】的控制台直接设置。
- 等待控制台域名状态验证通过,一般一两个小时完成验证,如果状态还是验证中,可以手动点击执行验证操作。
- 在阿里云【邮件推送】控制台,增加发信地址,并设置SMTP密码
- 【可选】在阿里云【邮件推送】控制台,设置标签。标签可以便于阿里云控制台统计。
3.3 总结
完成以上配置,即可进入Demo开发和测试阶段。
4. 收费模式
阿里云官方文档:阿里云发送邮件文档
4.1 免费额度
邮件推送的每个阿里云主账户,享有共2000封免费发信额度,每天最多免费发送200封。
如用户有免费发信额度,免费额度将不包含在资源包或按量计费范围内,超过免费额度的邮件量会产生费用。
4.2 资源包
资源包是预付费方式。资源包购买成功后,立即生效。
购买时长(即有效期):6 个月。每个资源包的有效期独立计算,多个资源包有效期不会叠加。
扣减方式:购买的资源包在有效期内,优先扣减资源包内的邮件量。多个资源包按有效期先结束的优先扣减,当所有资源包扣减完毕后,自动转为按量计费的方式扣减。
资源包使用后或资源包到期后,剩余流量不支持退订、不支持退款。
4.3 按量付费
按量付费是后付费方式,按照实际使用量计费,单价为 2 元/1000 封。该计费方式无须主动开通,邮件推送产品开通后,默认为按量付费。如果您购买了资源包,当资源包内的额度使用完之后,自动转按量付费。
计算公式为: 按量计费的费用 = 邮件发送量 / 1000 X 2.00 元。
费用精确到小数点后 2 位,即最低账单金额为 0.01 元。
5. Demo开发
5.1 选择SMTP服务器
不同站点的SMTP服务地址:
华东 1(杭州): smtpdm.aliyun.com
新加坡(新):smtpdm-ap-southeast-1.aliyuncs.com
美国(新):smtpdm-us-east-1.aliyuncs.com
德国:smtpdm-eu-central-1.aliyuncs.com
SMTP端口号:25,80,465(SSL加密)
注意:ECS 基于安全考虑,目前已禁用 25 端口。
请根据控制台选择的服务区域选择对应的服务器地址。
综上,Demo选择华东 1(杭州): smtpdm.aliyun.com,端口号使用80。
5.2 pom引用
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
5.3 demo代码
package org.example;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.*;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;
import java.util.UUID;
public class SampleMail {
// 配置常量
private static final String SMTP_HOST = "smtpdm.aliyun.com";
private static final int SMTP_PORT = 80;
private static final String USER_NAME = "之前注册的邮件域名";
private static final String PASSWORD = "邮件密码,在阿里云设置的";
protected static String genMessageID(String mailFrom) {
// 生成Message-ID:
if (!mailFrom.contains("@")) {
throw new IllegalArgumentException("Invalid email format: " + mailFrom);
}
String domain = mailFrom.split("@")[1];
UUID uuid = UUID.randomUUID();
return "<" + uuid.toString() + "@" + domain + ">";
}
private static void setRecipients(MimeMessage message, Message.RecipientType type, String[] recipients) throws MessagingException {
// 设置收件人地址
if (recipients == null || recipients.length == 0) {
return; // 空列表不设置
}
InternetAddress[] addresses = new InternetAddress[recipients.length];
for (int h = 0; h < recipients.length; h++) {
addresses[h] = new InternetAddress(recipients[h]);
}
message.setRecipients(type, addresses);
}
public static void main(String[] args) throws MessagingException, UnsupportedEncodingException {
// 配置发送邮件的环境属性
final Properties props = new Properties();
// 表示SMTP发送邮件,需要进行身份验证
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", SMTP_HOST);
//设置端口:
props.put("mail.smtp.port", SMTP_PORT);//或"25", 如果使用ssl,则去掉使用80或25端口的配置,进行如下配置:
//加密方式:
//props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
//props.put("mail.smtp.socketFactory.fallback", "false");//禁止回退非加密
//props.put("mail.smtp.socketFactory.port", "465");
//props.put("mail.smtp.port", "465");
props.put("mail.smtp.from", USER_NAME); //mailfrom 参数
props.put("mail.user", USER_NAME);// 发件人的账号(在控制台创建的发信地址)
props.put("mail.password", PASSWORD);// 发信地址的smtp密码(在控制台选择发信地址进行设置)
//props.put("mail.smtp.connectiontimeout", 1000);
System.setProperty("mail.mime.splitlongparameters", "false");//用于解决附件名过长导致的显示异常
//props.setProperty("mail.smtp.ssl.enable", "true"); //请配合465端口使用
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(USER_NAME, PASSWORD);
}
};
//使用环境属性和授权信息,创建邮件会话
Session mailSession = Session.getInstance(props, authenticator);
String messageIDValue = genMessageID(USER_NAME);
MimeMessage message = new MimeMessage(mailSession) {
@Override
protected void updateMessageID() throws MessagingException {
setHeader("Message-ID", messageIDValue);
}
};
try {
// 设置发件人邮件地址和名称。填写控制台配置的发信地址。和上面的mail.user保持一致。名称用户可以自定义填写。
InternetAddress from = new InternetAddress(USER_NAME, "发件人昵称");//from 参数,可实现代发,注意:代发容易被收信方拒信或进入垃圾箱。
message.setFrom(from);
setRecipients(message, Message.RecipientType.TO, new String[]{"接收邮件地址"});
InternetAddress replyToAddress = new InternetAddress("回信地址");
message.setReplyTo(new Address[]{replyToAddress});//可选。设置回信地址
message.setSentDate(new Date());
message.setSubject("测试主题");
//发送附件和内容:
// 创建多重消息
Multipart multipart = new MimeMultipart();
// 创建一个BodyPart用于HTML内容
BodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent("测试<br> html内容4", "text/html;charset=UTF-8");//设置邮件的内容,会覆盖前面的message.setContent
multipart.addBodyPart(htmlPart);
//附件部分
//发送附件,总的邮件大小不超过15M,创建消息部分。
// 发送本地附件
String[] fileList = {"C:\\Users\\18708\\Desktop\\test.txt"};
for (String filePath : fileList) {
MimeBodyPart mimeBodyPart = new MimeBodyPart();
FileDataSource fileDataSource = new FileDataSource(filePath);
mimeBodyPart.setDataHandler(new DataHandler(fileDataSource));
//处理附件名称中文(附带文件路径)乱码问题
mimeBodyPart.setFileName(MimeUtility.encodeWord(fileDataSource.getName()));
mimeBodyPart.addHeader("Content-Transfer-Encoding", "base64");
multipart.addBodyPart(mimeBodyPart);
}
// 添加完整消息
message.setContent(multipart);
// 发送附件代码,结束
mailSession.setDebug(true);//开启debug模式
Transport.send(message);
System.out.println("发送完成!");
} catch (MessagingException | UnsupportedEncodingException e) {
System.err.println("邮件发送失败: " + e.getMessage());
e.printStackTrace();
}
}
}
5.4 运行结果
正常运行之后,会log出发送完成,几秒钟之后会收到邮件,但是这里有个问题是,不管代码里写的收件箱是否是真实有效的,在log里也都是发送完成。也就是说,在发送的代码里,无法知道邮件是否真正投递到对方邮箱中。
6 总结
至此,发送邮件DEMO功能基本完成,不过只是个demo,要想在项目中使用,还需要考虑很多其他的问题,还要封装一层接口,目前的几个问题:垃圾邮件,使用QQ邮箱作为收件箱,有可能会进入到垃圾箱中,阿里云建议在收件方设置,将发送邮件的域名标记为白名单。发送记录:发送记录接口目前没找到(应该是没有),此接口程序可以自己实现,不需要使用阿里云接口。发送失败策略:初步思路:建立发送失败表,每隔一段时间查询失败的数据重新发送,成功之后清除。