写在前面


  由于公司最近有业务需求,需要实现批量发送邮件到指定邮箱的功能,就写了一个小的工具类,开发过程中经历了踩坑然后优化,感觉这个功能还是有些细节还是要注意的,值得写文记录一下,有在开发中碰到相同问题的各位朋友可以参考一下,希望对各位能有所帮助!

在这里插入图片描述


一、准备工作

1) 需求分析

公司开展业务 ——> 客户提出需求 ——> 产品拆解需求 ——> 领导安排开发 ——> 码农负责搬砖

 程序员的工作内容大抵是如此产生的,当我们是个小码农的时候还不会过多关注诸如需求是如何产生的、开发框架为什么要这样设计等问题,而是更多地着眼于当下,想办法使用各种工具完成自己的模块开发任务,这个过程难免会因为没有吃透需求被测试打回重做;但随着对公司业务的深入理解、对开发流程的不断熟悉以及综合能力的逐渐提升,我们慢慢地对需求也有了自己的理解,学会了从用户角度理解需求,也会慢慢重视自己写出来的代码,使得代码更为精简且优雅。

 像这样,由最初的单纯为了实现某个功能点,到后来明白需求决定了代码、代码服务于需求,也知道了重视代码质量,这大概也是我们作为开发必须经历的一种成长吧!

 sorry,扯远了,言归正传,这个业务需求无非是想在平台触发某个条件的同时用邮件通知到相关人员,而想要实现发送邮件的功能,我们需要满足以下必要条件:

1. 邮件收发平台

顾名思义,就是提供了接口让我们能接入收发邮件功能的一些平台,大家比较熟悉的有:
1、QQ邮箱,@qq.com
2、网易邮箱,@163.com或@126.com
3、新浪邮箱,@sina.cn
4、139邮箱,@139.com
5、谷歌邮箱,@gmail.com

还有可能不太熟悉的:tom邮箱(TOM集团公司推出)、Outlook(微软旗下)等。

怎么选???

POP3(拉):邮局协议3,特点是用户可以将服务器上的邮件保存至本地并且可以删除服务器上的邮件,主要用来从服务器上收邮件;
SMTP(推):简单邮件传输协议,特点是只有提供了账户名和密码之后才可登录 SMTP 服务器,避免收到垃圾邮件,安全性高;
IMAP(拉):交互式邮件存取协议;
MIME(推):多用途网际邮件扩充协议;
...
  • 其次,我们要从实际需求出发,确定自己的邮件协议类型,比如我的业务是发送文本信息到其他邮箱,起到通知的作用,这用SMTP协议就能实现,那就是它。

至于选择哪家的接口,这个要看公司的选择了,由于我公司运维甩给我的是公司的QQ邮箱,因此本文是围绕QQ邮箱展开的。

2. 邮件发送方
也就是邮件的发件人

3. 邮件内容
邮件的正文内容,可以是纯文本,也可以添加包含图片、文档、音视频等格式的附件,这个要看有没有添加附件的需求,也要看当前所选邮箱在这方面的限制(主要是指附件内容格式限制、文件大小限制等)。

4. 邮件接收方
目标邮箱地址,你希望谁收到邮件

2)邮箱的准备工作(正文开始!!!)

获取邮箱授权码

我们要准备好邮箱的授权码(一般是16位)(授权码是QQ邮箱用于登录第三方客户端的专用密码,适用于登录以下服务:POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务),授权码仅用于邮件客户端登录,而不能登录你的邮箱网页后台管理端,还是考虑到安全问题,即使泄露授权码,对方也无法登录网易或腾讯系统账号后台,这样影响也相对较小,从而保证邮箱的安全

① 用账号密码登录QQ邮箱,然后按图配置

在这里插入图片描述
② 通过短信验证然后得到邮箱授权码,在后面会用到。

在这里插入图片描述
③ 下图这里可以看到已经开启

在这里插入图片描述

二、代码部分

1)添加邮件依赖包

将下面的依赖包添加到你的pom文件中

<dependency>
    <groupId>javax.mail</groupId>
    <artifactId>mail</artifactId>
    <version>1.4.7</version>
</dependency>

maven坐标:https://mvnrepository.com/artifact/javax.mail/mail可以在这里找到你想要的版本的jar包

2)方法实现

1. 邮箱参数配置
使用下面哪个服务地址取决于发送还是接收邮件,发送的情况下还要考虑是一般邮箱还是企业邮箱

QQ邮箱地址及端口号:

接收邮件服务器: pop.qq.com,接收端口: 110或995(使用SSL时)
接收邮件服务器: imap.qq.com,接收端口: 143或993(使用SSL时)
一般发送邮件服务器: smtp.qq.com,发送端口: 25或465/587(使用SSL时)
企业邮箱发送邮件服务器: smtp.exmail.qq.com,发送端口: 465/587(使用SSL时)

注: 465端口是为SMTP SSL(SMTP-over-SSL)协议服务开放的,这是SMTP协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。

  • 如果是一般邮箱:
	// 邮件协议
	private static final String emailProtocol = "smtp";
	// 发件人的SMTP服务器地址(普通QQ邮箱)
	private static final String emailSMTPHost = "smtp.qq.com";
	// 端口
	private static final String emailPort = "465";
    // 发件人邮箱地址
    private static final String emailAccount = "1*********0@qq.com"; // 这个是普通QQ邮箱
    // 发件人邮箱授权码
    private static final String emailPassword = "qvq*********if";
	// 邮件协议
	private static final String emailProtocol = "smtp";
	// 发件人的SMTP服务器地址(腾讯企业邮箱
	private static final String emailSMTPHost = "smtp.exmail.qq.com";
	// 端口
	private static final String emailPort = "465";
    // 发件人邮箱地址
    private static final String emailAccount = "k******i@k***t.com"; // 这个是企业邮箱
    // 发件人邮箱授权码
    private static final String emailPassword = "qvq***********if";

2. 代码部分

package com.myProject.test.util;

import lombok.extern.log4j.Log4j2;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.Properties;

/**
 * @author TracyYang
 * @Description: 邮件工具类
 * @date 2023/7/22 14:15
 */
@Log4j2
public class MailUtils {

    // 邮件协议
	private static final String emailProtocol = "smtp";
	// 发件人的SMTP服务器地址(企业邮箱
	private static final String emailSMTPHost = "smtp.exmail.qq.com";
	// 端口
	private static final String emailPort = "465";
    // 发件人邮箱地址
    private static final String emailAccount = "k******i@k***t.com"; // 这个是企业邮箱
    // 发件人邮箱授权码
    private static final String emailPassword = "qvq***********if";

    /**
     * 发送邮件
     *
     * @param emails  目标邮件地址
     * @param title   邮件标题
     * @param content 邮件内容
     */
    public static boolean sendEmail(Set<String> emails, String title, String content) {
        // 未传收件人邮箱地址则直接返回
        if (emails == null || emails.isEmpty()) return false;
        try {
            // 1. 创建参数配置, 用于连接邮件服务器的参数配置
            Properties props = new Properties();
            props.setProperty("mail.transport.protocol", emailProtocol); // 使用的协议(JavaMail规范要求)
            props.setProperty("mail.smtp.host", emailSMTPHost); // 指定smtp服务器地址
            props.setProperty("mail.smtp.port", emailPort); // 指定smtp端口号
            // 使用smtp身份验证
            props.setProperty("mail.smtp.auth", "true"); // 需要请求认证
            props.put("mail.smtp.ssl.enable", "true"); // 开启SSL
            props.put("mail.smtp.ssl.protocols", "TLSv1.2"); // 指定SSL版本
            props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            // 由于Properties默认不限制请求时间,可能会导致线程阻塞,所以指定请求时长
            props.setProperty("mail.smtp.connectiontimeout", "10000");// 与邮件服务器建立连接的时间限制
            props.setProperty("mail.smtp.timeout", "10000");// 邮件smtp读取的时间限制
            props.setProperty("mail.smtp.writetimeout", "10000");// 邮件内容上传的时间限制
            // 2. 根据配置创建会话对象, 用于和邮件服务器交互
            Session session = Session.getDefaultInstance(props);
            session.setDebug(false); // 设置为debug模式, 可以查看详细的发送log
            // 3. 创建邮件
            MimeMessage message = new MimeMessage(session);
            // 4. From: 发件人(昵称有广告嫌疑,避免被邮件服务器误认为是滥发广告以至返回失败,请修改昵称)
            message.setFrom(new InternetAddress(emailAccount, "改成你的发件人名称", "UTF-8"));
            // 5. To: 收件人(可以增加多个收件人、抄送、密送)
            // MimeMessage.RecipientType.TO: 发送 MimeMessage.RecipientType.CC:抄送 MimeMessage.RecipientType.BCC:密送
            int size = emails.size();
            // 单个目标邮箱还是多个
            if (size == 1) {
                String email = emails.iterator().next();
                message.setRecipient(Message.RecipientType.TO, new InternetAddress(email, email, "UTF-8"));
            } else {
                InternetAddress[] addresses = new InternetAddress[emails.size()];
                int i = 0;
                for (String email : emails) {
                    addresses[i++] = new InternetAddress(email, email, "UTF-8");
                }
                message.setRecipients(MimeMessage.RecipientType.TO, addresses);
            }
            // 6. Subject: 邮件主题(标题有广告嫌疑,避免被邮件服务器误认为是滥发广告以至返回失败,请修改标题)
            message.setSubject(title, "UTF-8");
            // 7. Content: 邮件正文(可以使用html标签)(内容有广告嫌疑,避免被邮件服务器误认为是滥发广告以至返回失败,请修改发送内容)
            message.setContent(content, "text/html;charset=UTF-8");
            // 8. 设置发件时间
            message.setSentDate(new Date());
            // 9. 保存设置
            message.saveChanges();
            // 10. 根据 Session 获取邮件传输对象
            Transport transport = session.getTransport();
            transport.connect(emailAccount, emailPassword);
            // 11. 发送邮件, 发到所有的收件地址, message.getAllRecipients()获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
            transport.sendMessage(message, message.getAllRecipients());
            // 12. 关闭传输连接
            transport.close();
            return true;
        } catch (Exception e) {
            log.error("发送邮件失败,系统错误!", e);
            return false;
        }
    }

   public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("1*******0@qq.com");
        System.out.println(MailUtils.sendEmail(set, "测试发送邮件的接口!",
                "您好!这是我发送的一封测试发送接口的邮件,看完请删除记录。"));
    }

}

3. 执行结果
在这里插入图片描述

三、开发踩坑

报错一

1. 错误信息
javax.mail.AuthenticationFailedException: 535 Login Fail. Please enter your authorization code to login.

在这里插入图片描述

2. 可能原因及解决办法

  1. 可能是发件人邮箱的POP/SMTP服务没有开启或没有使用授权码,建议开启并确认使用授权码后再尝试
  2. 可能是发件人是企业邮箱却错误使用了smtp.qq.com服务地址,建议改为smtp.exmail.qq.com再尝试
  3. 可能是发件人邮箱授权码过期,建议重新申请一个再尝试

报错二

1. 错误信息
javax.mail.AuthenticationFailedException: 535 Error: authentication failed, system busy

在这里插入图片描述

2. 可能原因及解决办法

  1. 可能是发件人邮箱的POP/SMTP服务没有开启或没有使用授权码,建议开启并确认使用授权码后再尝试
  2. 可能是发件人是普通邮箱却错误使用了smtp.exmail.qq.com服务地址,建议改为smtp.qq.com再尝试
  3. 可能是发件人邮箱授权码过期,建议重新申请一个再尝试

报错三

1. 错误信息
javax.mail.MessagingException: Could not connect to SMTP host: smtp.exmail.qq.com, port: 465

在这里插入图片描述
2. 可能原因及解决办法

  1. 可能是发件人Properties开启了SSL( props.put(“mail.smtp.ssl.enable”, “true”); )但没有指定版本,建议指定SSL的版本,加上下面两个属性:
props.put("mail.smtp.ssl.protocols", "TLSv1.2");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

四、功能拓展

其实java是对邮件功能有封装的,下面两个jar包是邮件发送功能另一种实现方式需要用到的,相关博文我也贴出来了(蓝字直达),感兴趣的可以看下,希望能帮助到大家!

关于commons-email讲解的博文

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-email</artifactId>
    <version>1.5</version>
</dependency>

关于javax.activation讲解的博文

<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1</version>
</dependency>
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐