Java寄送電子郵件-使用gmail帳號

JavaMail是個歷史悠久的寄送電子郵件Email套件,由Sun公司所開發(Oracle),因為電子郵件數年來並沒有多大的改變,所以一直都是十分方便的一個套件,使用方法可參考Java程式Mail、EDM(電子型錄)寄送這篇。

而現在有了另一個framework,是由Apache組職所提供的開放原始碼套件Apache Commons email,使用上更為簡單,而且支援度也很高,像是gmail要使用javamail來寄送,就需要設定繁雜的Sectury項目,而Apache commons email就顯的簡單許多,底下的範例就是使用gmail的smtp.gmail.com來當SMTP服務寄送信件的,我把它應用在之前的留言板,當有人留言時,就可以馬上接到信件的通知。

留言板存檔後寄信

收到信件的內容

這範例需要的lib是Apache commons email,寄件的方法,需先建立一個Email主體,可以是SimpleEmail,寄送純文字,也可以是HtmlEmail,寄送html內容,也可以對Email主體進行attach附加文件的功能。

比較特別的是gmail需要使用到TLS或SSL加密,所以需要對Email主體設定setTSL為真及利用setAuthenticator來告知道驗證用的帳號及密碼。

原始碼如下:

mail.properties

host=smtp.gmail.com
port=587
[email protected]
[email protected]
pwd=tyu123
sleep=100
fromName=yslifes Board

Sender

這是寄Mail的Thread,寫成線程可以不用等待SMTP回報傳送結果,所以寄送成功與否並不會回傳到前端,如此可以減少前端等待寄Mail的時間。

package yslifes.mail.thread;

import java.util.*;
import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;

public class Sender extends java.lang.Thread {
    private String content;

    private org.apache.log4j.Logger log;
    private String subject;
    private String to;
    private static Properties p = null;

    public static void loadProperties() {
        try {
            p = new Properties();
            // log.info(this.getClass().getResource("/mail.properties").getPath());
            System.out.println(Sender.class.getResource("/mail.properties"));
            java.io.BufferedReader reader = new java.io.BufferedReader(
                    new java.io.FileReader(Sender.class.getResource(
                            "/mail.properties").getFile()));
            ;
            String str = null;
            while ((str = reader.readLine()) != null) {
                String sp[] = str.split("=");
                p.put(sp[0].trim(), sp[1].trim());
                System.out.println(sp[0].trim() + "," + sp[1].trim());
            }

            reader.close();
        } catch (java.io.IOException e) {

        }
    }

    public Sender(String to, String subject, String content) {
        super();
        if (p == null)
            loadProperties();
        this.content = content;
        this.to = to;
        log = org.apache.log4j.Logger.getLogger(this.getClass());
        // this.p = p;
        this.subject = subject;
    }

    public void run() {
        HtmlEmail email = new HtmlEmail();

        try {

            String host = p.getProperty("host");

            String from = p.getProperty("from");
            String from_name = p.getProperty("fromName");

            String user = p.getProperty("user");
            String pwd = p.getProperty("pwd");
            String port = p.getProperty("port");

            email.setTLS(true); // 是否TLS檢驗,某些email需要TLS安全檢驗,同理有SSL檢驗
            // email.setSSL(true);
            email.setHostName(host);
            email.setAuthenticator(new DefaultAuthenticator(user, pwd)); // 使用者帳號及密碼
            //email.setAuthentication(user, user);
            // email.setSslSmtpPort(port);
            email.setSmtpPort(Integer.parseInt(port));

            email.setFrom(from, from_name);
            email.setCharset("utf-8");
            
            email.addTo(to); // 接收方
            // email.addCc("[email protected]"); //副本
            // email.addBcc("[email protected]"); //密件副本
            email.setSubject(subject); // 標題

            // email.setTextMsg("Your email client does not support HTML messages");
            email.setHtmlMsg(content); // 内容
            email.send();

        } catch (EmailException e) {
            e.printStackTrace();
            log.info(e);
        } catch (Exception e) {
            e.printStackTrace();
            log.info(e);
        }
    }
    public static void main(String args[])
    {
        (new Sender("[email protected]","test","test")).start();
    }

}

如果想把這個範例使用到之前範例留言板,可以把程式碼加到postAction.jsp裡。

                    ps.setString(++idx, yslifes.tools.StringTool.reFormat(mail));
                    ps.setString(++idx, yslifes.tools.StringTool.reFormat(tel));
                    ps.setString(++idx, desc);
                    logger.debug("PostAction:" + desc);
                    ps.executeUpdate();
                    
                    (new yslifes.mail.thread.Sender("[email protected]",
                    "有人留言","<html><body>標題:"+title+"<br/>內容:"+desc+"</body></html>")).start();

2014/12/27備註

  • 還需要一個mail.jar在javamail專案裡有
  • http://www.oracle.com/technetwork/java/javamail/index-138643.html
  • 另外新版的apache HtmlMail不建議使用setTSL(true);可改用setStartTLSEnabled(true);

2016/11/30補充

上面的範例目前應該會出現

org.apache.commons.mail.EmailException: Sending the email to the following server failed : smtp.gmail.com:587
	at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1410)
	at org.apache.commons.mail.Email.send(Email.java:1437)
	at com.creations.utils.MailUtils.SendMail(MailUtils.java:103)
	at com.creations.utils.MailUtils.main(MailUtils.java:153)
Caused by: javax.mail.MessagingException: Could not convert socket to TLS;
  nested exception is:
	javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1907)
	at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:666)
	at javax.mail.Service.connect(Service.java:317)
	at javax.mail.Service.connect(Service.java:176)
	at javax.mail.Service.connect(Service.java:125)
	at javax.mail.Transport.send0(Transport.java:194)
	at javax.mail.Transport.send(Transport.java:124)
	at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1400)
	... 3 more
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1497)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:212)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
	at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:549)
	at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:486)
	at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1902)
	... 10 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1479)
	... 20 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
	... 26 more

這裡提供另一個範例

import javax.mail.MessagingException;
import javax.mail.internet.AddressException;

import java.util.Properties;
import javax.mail.Message;

import javax.mail.Session;
import javax.mail.Transport;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.mail.DefaultAuthenticator;
import org.apache.commons.mail.HtmlEmail;
import org.apache.log4j.Logger;
public class MailUtils {
	private static Properties mailServerProperties = null ;
	
	
	private static Logger logger = Logger.getLogger(MailUtils.class);
	public static void generateAndSendEmail(String[] to,String subject,String html) throws AddressException, MessagingException {
		 
		if(mailServerProperties==null){
		// Step1
			logger.info("\n 1st ===> setup Mail Server Properties..");
			mailServerProperties = System.getProperties();
			mailServerProperties.put("mail.smtp.port", "587");
			mailServerProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");
			mailServerProperties.put("mail.smtp.auth", "true");
			mailServerProperties.put("mail.smtp.starttls.enable", "true");
			logger.info("Mail Server Properties have been setup successfully..");
			
		}
		// Step2
		logger.info("\n\n 2nd ===> get Mail Session..");
		Session getMailSession = Session.getDefaultInstance(mailServerProperties, null);
		MimeMessage generateMailMessage = new MimeMessage(getMailSession);
		for(int i = 0 ; i < to.length;i++)
		generateMailMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to[i]));
		//generateMailMessage.addRecipient(Message.RecipientType.CC, new InternetAddress("[email protected]"));
		generateMailMessage.setSubject(subject);
		String emailBody = html;
		generateMailMessage.setContent(emailBody, "text/html; charset=UTF-8");
		logger.info("Mail Session has been created successfully..");
 
		// Step3
		logger.info("\n\n 3rd ===> Get Session and Send mail");
		Transport transport = getMailSession.getTransport("smtp");
 
		// Enter your correct gmail UserID and Password
		// if you have 2FA enabled then provide App Specific Password
		transport.connect("smtp.gmail.com",587, "yourGmail account", "yourGmail password");
		transport.sendMessage(generateMailMessage, generateMailMessage.getAllRecipients());
		transport.close();
	}

	public static void main(String args[]) throws AddressException, MessagingException{
		MailUtils.SendMail(new String[]{"[email protected]"}, "測試", "標題<br/>英文ABC 123<br/>111<a href='https://blog.yslifes.com'>點我進入blog</a> ");
	}
}

如果沒有加

mailServerProperties.put("mail.smtp.ssl.trust", "smtp.gmail.com");

會跟上面的範例有一樣的狀況

另外如果帳號有二階段認證的話可以參考這篇

Java MailAPI Example – Send an Email via GMail SMTP (TLS Authentication)

Getting error? How to triage an issue?

  • If you’ve turned on 2-Step Verification for your account, you might need to enter an App password.
  • Important: If you’re still having problems, visit http://www.google.com/accounts/DisplayUnlockCaptcha and sign in with your Gmail username and password. If necessary, enter the letters in the distorted picture.

2 thoughts to “Java寄送電子郵件-使用gmail帳號”

  1. 我下載了java6,卻下載不成,因為有個程式需要用到java6,所以我嘗試下載,第一次用可以,但用到後面又不小心刪了,但本來想要在下載一次,卻下載不成,請幫忙求解!

yku 發表迴響取消回覆