最近公司招聘系统有一个需求,就是给面试官发送面试邀请邮件的时候,需要发送一个面试提醒,能自动加入到日历中,面试前给面试官提醒,以免忘记。之前没做过发邮件功能,这次网上各种找资料,折腾了好久......
所需依赖:
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
Demo:
public class Test1 {
public static void main(String[] args) {
sendMeetingInvitationEmail();
}
private static Properties props;
private static Session session;
public static void sendMeetingInvitationEmail() {
try {
props = new Properties();
//发件人
String fromEmail = props.getProperty("fromEmail", "XXX@outlook.com");
//收件人(面试官)
String toEmail = props.getProperty("toEmail", "XXX@outlook.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.host", "smtp.office365.com");
//当前smtp host设为可信任 否则抛出javax.mail.MessagingException: Could not convert socket to TLS
props.put("mail.smtp.ssl.trust", "smtp.office365.com");
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl", "true");
//开启debug调试,控制台会打印相关信息
props.put("mail.debug", "true");
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
//发件人邮箱账号
String userId = props.getProperty("userId", "XXX@outlook.com");
//发件人邮箱密码(qq、163等邮箱用的是授权码,outlook是密码)
String password = props.getProperty("password", "XXXXXXXX");
return new PasswordAuthentication(userId, password);
}
};
session = Session.getInstance(props, authenticator);
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(fromEmail));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
//标题
message.setSubject("XXX公司诚邀应聘");
//面试开始时间
String startTime = getUtc("2019-09-04 14:00");
//面试结束时间
String endTime = getUtc("2019-09-04 15:00");
StringBuffer buffer = new StringBuffer();
buffer.append("BEGIN:VCALENDAR\n"
+ "PRODID:-//Microsoft Corporation//Outlook 9.0 MIMEDIR//EN\n"
+ "VERSION:2.0\n"
+ "METHOD:REQUEST\n"
+ "BEGIN:VEVENT\n"
//参会者
+ "ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:你和应聘者\n"
//组织者
//+ "ORGANIZER:MAILTO:张三\n"
+ "DTSTART:" + startTime + "\n"
+ "DTEND:" + endTime + "\n"
//面试地点
+ "LOCATION:会议室01\n"
//如果id相同的话,outlook会认为是同一个会议请求,所以使用uuid。
+ "UID:" + UUID.randomUUID().toString() + "\n"
+ "CATEGORIES:\n"
//会议描述
//+ "DESCRIPTION:Stay Hungry.<br>Stay Foolish.\n\n"
+ "SUMMARY:面试邀请\n" + "PRIORITY:5\n"
+ "CLASS:PUBLIC\n" + "BEGIN:VALARM\n"
//提前10分钟提醒
+ "TRIGGER:-PT10M\n" + "ACTION:DISPLAY\n"
+ "DESCRIPTION:Reminder\n" + "END:VALARM\n"
+ "END:VEVENT\n" + "END:VCALENDAR");
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(new DataHandler(new ByteArrayDataSource(buffer.toString(),
"text/calendar;method=REQUEST;charset=\"UTF-8\"")));
MimeMultipart multipart = new MimeMultipart();
MimeBodyPart mimeBodyPart = new MimeBodyPart();
//String emailText = getHtmlContent(sendEmailApi.getTemplateContent(tempValue),tempMap);
//文本类型正文
mimeBodyPart.setText("尊敬的张三:\r您好!\r特邀您...");
//html类型正文
//mimeBodyPart.setContent(emailText,"text/html;charset=UTF-8");
//添加正文
multipart.addBodyPart(mimeBodyPart);
//添加日历
multipart.addBodyPart(messageBodyPart);
message.setContent(multipart);
message.setSentDate(new Date());
message.saveChanges();
Transport.send(message);
} catch (MessagingException me) {
me.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 转utc时间
*
* @param str
* @return
*/
private static String getUtc(String str) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
long millionSeconds = 0;
try {
millionSeconds = sdf.parse(str).getTime();
} catch (ParseException e1) {
e1.printStackTrace();
}
//utc时间差8小时
long currentTime = millionSeconds - 8 * 60 * 60 * 1000;
Date date = new Date(currentTime);
//格式化日期
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = "";
nowTime = df.format(date);
//转换utc时间
String utcTime = nowTime.replace("-", "").replace(" ", "T").replace(":", "");
return utcTime;
}
}
日历功能的话,ical4j也是可以实现的,我这里既然也可实现同样的效果,就偷了个懒。
发送成功的效果:
注意事项:
这样的需求虽然不是满大街,但是网上的帖子也不是少的可怜那种,看着就这么点代码,真的折腾了好久,不是报这个错就是报那个错......
- 发件人邮箱密码:
如果是qq,163等的邮箱,用的是授权码,关于授权码如何获取,请自行百度。如果是outlook邮箱,因为outlook没有什么授权码,用的就是邮箱的登录密码。
- 服务器地址,端口参数:
props.put("mail.smtp.port", "587");
props.put("mail.smtp.host", "smtp.office365.com");
outlook邮箱如果是个人邮箱,请登录邮箱后设置里面去找(应该都是一样的吧),如下:
outlook邮箱如果不是个人邮箱,是公司邮箱,比如邮箱后缀是@XX公司.com,邮箱服务器地址就是公司的邮箱 服务器地址,端口好像就是常用的25吧。
如果是qq,163邮箱,端口就是25,服务器地址分别为smtp.qq.com、smtp.163.com。其他邮箱的话,抱歉没 有试过。
- 证书的问题:
javax.mail.MessagingException: Could not convert socket to TLS
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
不能把socket解析为TLS,不能对访问的目标提供有效证书。
在公司运行的时候,报了这个错。家里用自己outlook账号的话,倒是不会有任何问题...,不用加如下配置,证书也不用导入。
- 可以加这个配置,可以把当前邮箱服务器地址设为可信任的。
props.put("mail.smtp.ssl.trust", "smtp.office365.com");//邮箱服务器地址
-
另外还可以导入证书
用工具类生成安全证书或者从浏览器下载证书,放在\JAVA_HOME\jre\lib\security下面
工具类生成证书:
需要注意的是,帖子里面先javac编译java文件,后执行class文件,执行方式:
Java InstallCert hostname
hostname 就是发件邮箱的服务器地址,如smtp.office365.com。
浏览器下载证书:
https://blog.csdn.net/frankcheng5143/article/details/52164939
- 日历参数-参会者:
//参会者
+ "ATTENDEE;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:你和应聘者\n"
这个参数我在公司明明是注释的,没有加,可以发送邮件,不知道为什么回到家用自己邮箱发送,不加的话就会报错,一脸懵逼......,现在都还不知道怎么回事。
//无法发送邮件,因为他不包含任何收件人
com.sun.mail.smtp.SMTPSendFailedException: 554 5.2.0 STOREDRV.Submission.Exception:InvalidRecipientsException; Failed to process message due to a permanent exception with message A message can't be sent because it contains no recipients. InvalidRecipientsException: A message can't be sent because it contains no recipients. [Hostname=PS2PR02MB2725.apcprd02.prod.outlook.com]
另外outlook邮箱如果没有绑定手机号(需要验证账户),发送邮件也是会报错的。
- 身份验证失败:
javax.mail.SendFailedException: Send failure (javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful)
这个问题有点坑人啊,线上环境时不时的发不出邮件,后来一看就是报这个错,邮箱账号密码没错啊......
后来经过排查,发现是这个配置的问题,注释掉就可以了。
props.put("mail.smtp.auth", "true");
这个配置的意思是:是否需要身份验证(默认不验证),公司的邮箱服务器是不需要的,所以注释了也可以发,然后session里面的验证信息也不用传了,不需要账号密码。
session = Session.getInstance(props);
至于为什么设置了需要验证,传了账号密码,有时能发邮件,有时发不出那就不知道了.....这里先记录一下,如果哪位大神知道还望指出。