使用JavaMail向outlook发送会议邀请(日历)邮件

最近公司招聘系统有一个需求,就是给面试官发送面试邀请邮件的时候,需要发送一个面试提醒,能自动加入到日历中,面试前给面试官提醒,以免忘记。之前没做过发邮件功能,这次网上各种找资料,折腾了好久......

所需依赖:

<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也是可以实现的,我这里既然也可实现同样的效果,就偷了个懒。

发送成功的效果:

image

注意事项:

这样的需求虽然不是满大街,但是网上的帖子也不是少的可怜那种,看着就这么点代码,真的折腾了好久,不是报这个错就是报那个错......

  • 发件人邮箱密码:

如果是qq,163等的邮箱,用的是授权码,关于授权码如何获取,请自行百度。如果是outlook邮箱,因为outlook没有什么授权码,用的就是邮箱的登录密码。

  • 服务器地址,端口参数:
props.put("mail.smtp.port", "587");
props.put("mail.smtp.host", "smtp.office365.com");

outlook邮箱如果是个人邮箱,请登录邮箱后设置里面去找(应该都是一样的吧),如下:

outlookSMTP.jpg
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账号的话,倒是不会有任何问题...,不用加如下配置,证书也不用导入。

  1. 可以加这个配置,可以把当前邮箱服务器地址设为可信任的。
props.put("mail.smtp.ssl.trust", "smtp.office365.com");//邮箱服务器地址
  1. 另外还可以导入证书

    用工具类生成安全证书或者从浏览器下载证书,放在\JAVA_HOME\jre\lib\security下面

    工具类生成证书:

https://www.tuicool.com/articles/zUjiIb

需要注意的是,帖子里面先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);

至于为什么设置了需要验证,传了账号密码,有时能发邮件,有时发不出那就不知道了.....这里先记录一下,如果哪位大神知道还望指出。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容