Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.mail; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Store; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeUtility; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; /** * The base class for all email messages. This class sets the * sender's email & name, receiver's email & name, subject, and the * sent date. * <p> * Subclasses are responsible for setting the message body. * * @since 1.0 * @version $Id: Email.java 1665699 2015-03-10 21:12:09Z tn $ */ public abstract class Email { /** @deprecated since 1.3, use {@link EmailConstants#SENDER_EMAIL} instead */ @Deprecated public static final String SENDER_EMAIL = EmailConstants.SENDER_EMAIL; /** @deprecated since 1.3, use {@link EmailConstants#SENDER_NAME} instead */ @Deprecated public static final String SENDER_NAME = EmailConstants.SENDER_NAME; /** @deprecated since 1.3, use {@link EmailConstants#RECEIVER_EMAIL} instead */ @Deprecated public static final String RECEIVER_EMAIL = EmailConstants.RECEIVER_EMAIL; /** @deprecated since 1.3, use {@link EmailConstants#RECEIVER_NAME} instead */ @Deprecated public static final String RECEIVER_NAME = EmailConstants.RECEIVER_NAME; /** @deprecated since 1.3, use {@link EmailConstants#EMAIL_SUBJECT} instead */ @Deprecated public static final String EMAIL_SUBJECT = EmailConstants.EMAIL_SUBJECT; /** @deprecated since 1.3, use {@link EmailConstants#EMAIL_BODY} instead */ @Deprecated public static final String EMAIL_BODY = EmailConstants.EMAIL_BODY; /** @deprecated since 1.3, use {@link EmailConstants#CONTENT_TYPE} instead */ @Deprecated public static final String CONTENT_TYPE = EmailConstants.CONTENT_TYPE; /** @deprecated since 1.3, use {@link EmailConstants#ATTACHMENTS} instead */ @Deprecated public static final String ATTACHMENTS = EmailConstants.ATTACHMENTS; /** @deprecated since 1.3, use {@link EmailConstants#FILE_SERVER} instead */ @Deprecated public static final String FILE_SERVER = EmailConstants.FILE_SERVER; /** @deprecated since 1.3, use {@link EmailConstants#KOI8_R} instead */ @Deprecated public static final String KOI8_R = EmailConstants.KOI8_R; /** @deprecated since 1.3, use {@link EmailConstants#ISO_8859_1} instead */ @Deprecated public static final String ISO_8859_1 = EmailConstants.ISO_8859_1; /** @deprecated since 1.3, use {@link EmailConstants#US_ASCII} instead */ @Deprecated public static final String US_ASCII = EmailConstants.US_ASCII; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_DEBUG} instead */ @Deprecated public static final String MAIL_DEBUG = EmailConstants.MAIL_DEBUG; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_HOST} instead */ @Deprecated public static final String MAIL_HOST = EmailConstants.MAIL_HOST; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_PORT} instead */ @Deprecated public static final String MAIL_PORT = EmailConstants.MAIL_PORT; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_FROM} instead */ @Deprecated public static final String MAIL_SMTP_FROM = EmailConstants.MAIL_SMTP_FROM; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_AUTH} instead */ @Deprecated public static final String MAIL_SMTP_AUTH = EmailConstants.MAIL_SMTP_AUTH; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_USER} instead */ @Deprecated public static final String MAIL_SMTP_USER = EmailConstants.MAIL_SMTP_USER; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_PASSWORD} instead */ @Deprecated public static final String MAIL_SMTP_PASSWORD = EmailConstants.MAIL_SMTP_PASSWORD; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_TRANSPORT_PROTOCOL} instead */ @Deprecated public static final String MAIL_TRANSPORT_PROTOCOL = EmailConstants.MAIL_TRANSPORT_PROTOCOL; /** @deprecated since 1.3, use {@link EmailConstants#SMTP} instead */ @Deprecated public static final String SMTP = EmailConstants.SMTP; /** @deprecated since 1.3, use {@link EmailConstants#TEXT_HTML} instead */ @Deprecated public static final String TEXT_HTML = EmailConstants.TEXT_HTML; /** @deprecated since 1.3, use {@link EmailConstants#TEXT_PLAIN} instead */ @Deprecated public static final String TEXT_PLAIN = EmailConstants.TEXT_PLAIN; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_TRANSPORT_TLS} instead */ @Deprecated public static final String MAIL_TRANSPORT_TLS = EmailConstants.MAIL_TRANSPORT_TLS; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_FALLBACK} instead */ @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_CLASS} instead */ @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_PORT} instead */ @Deprecated public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_CONNECTIONTIMEOUT} instead */ @Deprecated public static final String MAIL_SMTP_CONNECTIONTIMEOUT = EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT; /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_TIMEOUT} instead */ @Deprecated public static final String MAIL_SMTP_TIMEOUT = EmailConstants.MAIL_SMTP_TIMEOUT; /** The email message to send. */ protected MimeMessage message; /** The charset to use for this message. */ protected String charset; /** The Address of the sending party, mandatory. */ protected InternetAddress fromAddress; /** The Subject. */ protected String subject; /** An attachment. */ protected MimeMultipart emailBody; /** The content. */ protected Object content; /** The content type. */ protected String contentType; /** Set session debugging on or off. */ protected boolean debug; /** Sent date. */ protected Date sentDate; /** * Instance of an <code>Authenticator</code> object that will be used * when authentication is requested from the mail server. */ protected Authenticator authenticator; /** * The hostname of the mail server with which to connect. If null will try * to get property from system.properties. If still null, quit. */ protected String hostName; /** * The port number of the mail server to connect to. * Defaults to the standard port ( 25 ). */ protected String smtpPort = "25"; /** * The port number of the SSL enabled SMTP server; * defaults to the standard port, 465. */ protected String sslSmtpPort = "465"; /** List of "to" email addresses. */ protected List<InternetAddress> toList = new ArrayList<InternetAddress>(); /** List of "cc" email addresses. */ protected List<InternetAddress> ccList = new ArrayList<InternetAddress>(); /** List of "bcc" email addresses. */ protected List<InternetAddress> bccList = new ArrayList<InternetAddress>(); /** List of "replyTo" email addresses. */ protected List<InternetAddress> replyList = new ArrayList<InternetAddress>(); /** * Address to which undeliverable mail should be sent. * Because this is handled by JavaMail as a String property * in the mail session, this property is of type <code>String</code> * rather than <code>InternetAddress</code>. */ protected String bounceAddress; /** * Used to specify the mail headers. Example: * * X-Mailer: Sendmail, X-Priority: 1( highest ) * or 2( high ) 3( normal ) 4( low ) and 5( lowest ) * Disposition-Notification-To: user@domain.net */ protected Map<String, String> headers = new HashMap<String, String>(); /** * Used to determine whether to use pop3 before smtp, and if so the settings. */ protected boolean popBeforeSmtp; /** the host name of the pop3 server. */ protected String popHost; /** the user name to log into the pop3 server. */ protected String popUsername; /** the password to log into the pop3 server. */ protected String popPassword; /** * Does server require TLS encryption for authentication? * @deprecated since 1.3, use setStartTLSEnabled() instead */ @Deprecated protected boolean tls; /** * Does the current transport use SSL/TLS encryption upon connection? * @deprecated since 1.3, use setSSLOnConnect() instead */ @Deprecated protected boolean ssl; /** socket I/O timeout value in milliseconds. */ protected int socketTimeout = EmailConstants.SOCKET_TIMEOUT_MS; /** socket connection timeout value in milliseconds. */ protected int socketConnectionTimeout = EmailConstants.SOCKET_TIMEOUT_MS; /** * If true, enables the use of the STARTTLS command (if supported by * the server) to switch the connection to a TLS-protected connection * before issuing any login commands. Note that an appropriate trust * store must configured so that the client will trust the server's * certificate. * Defaults to false. */ private boolean startTlsEnabled; /** * If true, requires the use of the STARTTLS command. If the server doesn't * support the STARTTLS command, or the command fails, the connect method * will fail. * Defaults to false. */ private boolean startTlsRequired; /** does the current transport use SSL/TLS encryption upon connection? */ private boolean sslOnConnect; /** * If set to true, check the server identity as specified by RFC 2595. These * additional checks based on the content of the server's certificate are * intended to prevent man-in-the-middle attacks. * Defaults to false. */ private boolean sslCheckServerIdentity; /** * If set to true, and a message has some valid and some invalid addresses, send the message anyway, * reporting the partial failure with a SendFailedException. * If set to false (the default), the message is not sent to any of the recipients * if there is an invalid recipient address. * Defaults to false. */ private boolean sendPartial; /** The Session to mail with. */ private Session session; /** * Setting to true will enable the display of debug information. * * @param d A boolean. * @since 1.0 */ public void setDebug(final boolean d) { this.debug = d; } /** * Sets the userName and password if authentication is needed. If this * method is not used, no authentication will be performed. * <p> * This method will create a new instance of * <code>DefaultAuthenticator</code> using the supplied parameters. * * @param userName User name for the SMTP server * @param password password for the SMTP server * @see DefaultAuthenticator * @see #setAuthenticator * @since 1.0 */ public void setAuthentication(final String userName, final String password) { this.setAuthenticator(new DefaultAuthenticator(userName, password)); } /** * Sets the <code>Authenticator</code> to be used when authentication * is requested from the mail server. * <p> * This method should be used when your outgoing mail server requires * authentication. Your mail server must also support RFC2554. * * @param newAuthenticator the <code>Authenticator</code> object. * @see Authenticator * @since 1.0 */ public void setAuthenticator(final Authenticator newAuthenticator) { this.authenticator = newAuthenticator; } /** * Set the charset of the message. Please note that you should set the charset before * adding the message content. * * @param newCharset A String. * @throws java.nio.charset.IllegalCharsetNameException if the charset name is invalid * @throws java.nio.charset.UnsupportedCharsetException if no support for the named charset * exists in the current JVM * @since 1.0 */ public void setCharset(final String newCharset) { final Charset set = Charset.forName(newCharset); this.charset = set.name(); } /** * Set the emailBody to a MimeMultiPart * * @param aMimeMultipart aMimeMultipart * @since 1.0 */ public void setContent(final MimeMultipart aMimeMultipart) { this.emailBody = aMimeMultipart; } /** * Set the content and contentType. * * @param aObject aObject * @param aContentType aContentType * @since 1.0 */ public void setContent(final Object aObject, final String aContentType) { this.content = aObject; this.updateContentType(aContentType); } /** * Update the contentType. * * @param aContentType aContentType * @since 1.2 */ public void updateContentType(final String aContentType) { if (EmailUtils.isEmpty(aContentType)) { this.contentType = null; } else { // set the content type this.contentType = aContentType; // set the charset if the input was properly formed final String strMarker = "; charset="; int charsetPos = aContentType.toLowerCase().indexOf(strMarker); if (charsetPos != -1) { // find the next space (after the marker) charsetPos += strMarker.length(); final int intCharsetEnd = aContentType.toLowerCase().indexOf(" ", charsetPos); if (intCharsetEnd != -1) { this.charset = aContentType.substring(charsetPos, intCharsetEnd); } else { this.charset = aContentType.substring(charsetPos); } } else { // use the default charset, if one exists, for messages // whose content-type is some form of text. if (this.contentType.startsWith("text/") && EmailUtils.isNotEmpty(this.charset)) { final StringBuffer contentTypeBuf = new StringBuffer(this.contentType); contentTypeBuf.append(strMarker); contentTypeBuf.append(this.charset); this.contentType = contentTypeBuf.toString(); } } } } /** * Set the hostname of the outgoing mail server. * * @param aHostName aHostName * @since 1.0 */ public void setHostName(final String aHostName) { checkSessionAlreadyInitialized(); this.hostName = aHostName; } /** * Set or disable the STARTTLS encryption. Please see EMAIL-105 * for the reasons of deprecation. * * @deprecated since 1.3, use setStartTLSEnabled() instead * @param withTLS true if STARTTLS requested, false otherwise * @since 1.1 */ @Deprecated public void setTLS(final boolean withTLS) { setStartTLSEnabled(withTLS); } /** * Set or disable the STARTTLS encryption. * * @param startTlsEnabled true if STARTTLS requested, false otherwise * @return An Email. * @since 1.3 */ public Email setStartTLSEnabled(final boolean startTlsEnabled) { checkSessionAlreadyInitialized(); this.startTlsEnabled = startTlsEnabled; this.tls = startTlsEnabled; return this; } /** * Set or disable the required STARTTLS encryption. * * @param startTlsRequired true if STARTTLS requested, false otherwise * @return An Email. * @since 1.3 */ public Email setStartTLSRequired(final boolean startTlsRequired) { checkSessionAlreadyInitialized(); this.startTlsRequired = startTlsRequired; return this; } /** * Set the port number of the outgoing mail server. * * @param aPortNumber aPortNumber * @throws IllegalArgumentException if the port number is < 1 * @since 1.0 */ public void setSmtpPort(final int aPortNumber) { checkSessionAlreadyInitialized(); if (aPortNumber < 1) { throw new IllegalArgumentException( "Cannot connect to a port number that is less than 1 ( " + aPortNumber + " )"); } this.smtpPort = Integer.toString(aPortNumber); } /** * Supply a mail Session object to use. Please note that passing * a user name and password (in the case of mail authentication) will * create a new mail session with a DefaultAuthenticator. This is a * convenience but might come unexpected. * * If mail authentication is used but NO username and password * is supplied the implementation assumes that you have set a * authenticator and will use the existing mail session (as expected). * * @param aSession mail session to be used * @throws IllegalArgumentException if the session is {@code null} * @since 1.0 */ public void setMailSession(final Session aSession) { EmailUtils.notNull(aSession, "no mail session supplied"); final Properties sessionProperties = aSession.getProperties(); final String auth = sessionProperties.getProperty(MAIL_SMTP_AUTH); if ("true".equalsIgnoreCase(auth)) { final String userName = sessionProperties.getProperty(MAIL_SMTP_USER); final String password = sessionProperties.getProperty(MAIL_SMTP_PASSWORD); if (EmailUtils.isNotEmpty(userName) && EmailUtils.isNotEmpty(password)) { // only create a new mail session with an authenticator if // authentication is required and no user name is given this.authenticator = new DefaultAuthenticator(userName, password); this.session = Session.getInstance(sessionProperties, this.authenticator); } else { // assume that the given mail session contains a working authenticator this.session = aSession; } } else { this.session = aSession; } } /** * Supply a mail Session object from a JNDI directory. * * @param jndiName name of JNDI resource (javax.mail.Session type), resource * if searched in java:comp/env if name does not start with "java:" * @throws IllegalArgumentException if the JNDI name is null or empty * @throws NamingException if the resource cannot be retrieved from JNDI directory * @since 1.1 */ public void setMailSessionFromJNDI(final String jndiName) throws NamingException { if (EmailUtils.isEmpty(jndiName)) { throw new IllegalArgumentException("JNDI name missing"); } Context ctx = null; if (jndiName.startsWith("java:")) { ctx = new InitialContext(); } else { ctx = (Context) new InitialContext().lookup("java:comp/env"); } this.setMailSession((Session) ctx.lookup(jndiName)); } /** * Determines the mail session used when sending this Email, creating * the Session if necessary. When a mail session is already * initialized setting the session related properties will cause * an IllegalStateException. * * @return A Session. * @throws EmailException if the host name was not set * @since 1.0 */ public Session getMailSession() throws EmailException { if (this.session == null) { final Properties properties = new Properties(System.getProperties()); properties.setProperty(MAIL_TRANSPORT_PROTOCOL, SMTP); if (EmailUtils.isEmpty(this.hostName)) { this.hostName = properties.getProperty(MAIL_HOST); } if (EmailUtils.isEmpty(this.hostName)) { throw new EmailException("Cannot find valid hostname for mail session"); } properties.setProperty(MAIL_PORT, this.smtpPort); properties.setProperty(MAIL_HOST, this.hostName); properties.setProperty(MAIL_DEBUG, String.valueOf(this.debug)); properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE, isStartTLSEnabled() ? "true" : "false"); properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_REQUIRED, isStartTLSRequired() ? "true" : "false"); properties.setProperty(EmailConstants.MAIL_SMTP_SEND_PARTIAL, isSendPartial() ? "true" : "false"); properties.setProperty(EmailConstants.MAIL_SMTPS_SEND_PARTIAL, isSendPartial() ? "true" : "false"); if (this.authenticator != null) { properties.setProperty(MAIL_SMTP_AUTH, "true"); } if (isSSLOnConnect()) { properties.setProperty(MAIL_PORT, this.sslSmtpPort); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_PORT, this.sslSmtpPort); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory"); properties.setProperty(MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false"); } if ((isSSLOnConnect() || isStartTLSEnabled()) && isSSLCheckServerIdentity()) { properties.setProperty(EmailConstants.MAIL_SMTP_SSL_CHECKSERVERIDENTITY, "true"); } if (this.bounceAddress != null) { properties.setProperty(MAIL_SMTP_FROM, this.bounceAddress); } if (this.socketTimeout > 0) { properties.setProperty(MAIL_SMTP_TIMEOUT, Integer.toString(this.socketTimeout)); } if (this.socketConnectionTimeout > 0) { properties.setProperty(MAIL_SMTP_CONNECTIONTIMEOUT, Integer.toString(this.socketConnectionTimeout)); } // changed this (back) to getInstance due to security exceptions // caused when testing using maven this.session = Session.getInstance(properties, this.authenticator); } return this.session; } /** * Set the FROM field of the email to use the specified address. The email * address will also be used as the personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email setFrom(final String email) throws EmailException { return setFrom(email, null); } /** * Set the FROM field of the email to use the specified address and the * specified personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @param name A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email setFrom(final String email, final String name) throws EmailException { return setFrom(email, name, this.charset); } /** * Set the FROM field of the email to use the specified address, personal * name, and charset encoding for the name. * * @param email A String. * @param name A String. * @param charset The charset to encode the name with. * @return An Email. * @throws EmailException Indicates an invalid email address or charset. * @since 1.1 */ public Email setFrom(final String email, final String name, final String charset) throws EmailException { this.fromAddress = createInternetAddress(email, name, charset); return this; } /** * Add a recipient TO to the email. The email * address will also be used as the personal name. * The name will be encoded by the charset of * {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email addTo(final String email) throws EmailException { return addTo(email, null); } /** * Add a list of TO recipients to the email. The email * addresses will also be used as the personal names. * The names will be encoded by the charset of * {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param emails A String array. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.3 */ public Email addTo(final String... emails) throws EmailException { if (emails == null || emails.length == 0) { throw new EmailException("Address List provided was invalid"); } for (final String email : emails) { addTo(email, null); } return this; } /** * Add a recipient TO to the email using the specified address and the * specified personal name. * The name will be encoded by the charset of * {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @param name A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email addTo(final String email, final String name) throws EmailException { return addTo(email, name, this.charset); } /** * Add a recipient TO to the email using the specified address, personal * name, and charset encoding for the name. * * @param email A String. * @param name A String. * @param charset The charset to encode the name with. * @return An Email. * @throws EmailException Indicates an invalid email address or charset. * @since 1.1 */ public Email addTo(final String email, final String name, final String charset) throws EmailException { this.toList.add(createInternetAddress(email, name, charset)); return this; } /** * Set a list of "TO" addresses. All elements in the specified * <code>Collection</code> are expected to be of type * <code>java.mail.internet.InternetAddress</code>. * * @param aCollection collection of <code>InternetAddress</code> objects. * @return An Email. * @throws EmailException Indicates an invalid email address. * @see javax.mail.internet.InternetAddress * @since 1.0 */ public Email setTo(final Collection<InternetAddress> aCollection) throws EmailException { if (aCollection == null || aCollection.isEmpty()) { throw new EmailException("Address List provided was invalid"); } this.toList = new ArrayList<InternetAddress>(aCollection); return this; } /** * Add a recipient CC to the email. The email * address will also be used as the personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email addCc(final String email) throws EmailException { return this.addCc(email, null); } /** * Add an array of CC recipients to the email. The email * addresses will also be used as the personal name. * The names will be encoded by the charset of * {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param emails A String array. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.3 */ public Email addCc(final String... emails) throws EmailException { if (emails == null || emails.length == 0) { throw new EmailException("Address List provided was invalid"); } for (final String email : emails) { addCc(email, null); } return this; } /** * Add a recipient CC to the email using the specified address and the * specified personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @param name A String. * @return An Email. * @throws EmailException Indicates an invalid email address. * @since 1.0 */ public Email addCc(final String email, final String name) throws EmailException { return addCc(email, name, this.charset); } /** * Add a recipient CC to the email using the specified address, personal * name, and charset encoding for the name. * * @param email A String. * @param name A String. * @param charset The charset to encode the name with. * @return An Email. * @throws EmailException Indicates an invalid email address or charset. * @since 1.1 */ public Email addCc(final String email, final String name, final String charset) throws EmailException { this.ccList.add(createInternetAddress(email, name, charset)); return this; } /** * Set a list of "CC" addresses. All elements in the specified * <code>Collection</code> are expected to be of type * <code>java.mail.internet.InternetAddress</code>. * * @param aCollection collection of <code>InternetAddress</code> objects. * @return An Email. * @throws EmailException Indicates an invalid email address. * @see javax.mail.internet.InternetAddress * @since 1.0 */ public Email setCc(final Collection<InternetAddress> aCollection) throws EmailException { if (aCollection == null || aCollection.isEmpty()) { throw new EmailException("Address List provided was invalid"); } this.ccList = new ArrayList<InternetAddress>(aCollection); return this; } /** * Add a blind BCC recipient to the email. The email * address will also be used as the personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.0 */ public Email addBcc(final String email) throws EmailException { return this.addBcc(email, null); } /** * Add an array of blind BCC recipients to the email. The email * addresses will also be used as the personal name. * The names will be encoded by the charset of * {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param emails A String array. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.3 */ public Email addBcc(final String... emails) throws EmailException { if (emails == null || emails.length == 0) { throw new EmailException("Address List provided was invalid"); } for (final String email : emails) { addBcc(email, null); } return this; } /** * Add a blind BCC recipient to the email using the specified address and * the specified personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @param name A String. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.0 */ public Email addBcc(final String email, final String name) throws EmailException { return addBcc(email, name, this.charset); } /** * Add a blind BCC recipient to the email using the specified address, * personal name, and charset encoding for the name. * * @param email A String. * @param name A String. * @param charset The charset to encode the name with. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.1 */ public Email addBcc(final String email, final String name, final String charset) throws EmailException { this.bccList.add(createInternetAddress(email, name, charset)); return this; } /** * Set a list of "BCC" addresses. All elements in the specified * <code>Collection</code> are expected to be of type * <code>java.mail.internet.InternetAddress</code>. * * @param aCollection collection of <code>InternetAddress</code> objects * @return An Email. * @throws EmailException Indicates an invalid email address * @see javax.mail.internet.InternetAddress * @since 1.0 */ public Email setBcc(final Collection<InternetAddress> aCollection) throws EmailException { if (aCollection == null || aCollection.isEmpty()) { throw new EmailException("Address List provided was invalid"); } this.bccList = new ArrayList<InternetAddress>(aCollection); return this; } /** * Add a reply to address to the email. The email * address will also be used as the personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.0 */ public Email addReplyTo(final String email) throws EmailException { return this.addReplyTo(email, null); } /** * Add a reply to address to the email using the specified address and * the specified personal name. * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}. * If it is not set, it will be encoded using * the Java platform's default charset (UTF-16) if it contains * non-ASCII characters; otherwise, it is used as is. * * @param email A String. * @param name A String. * @return An Email. * @throws EmailException Indicates an invalid email address * @since 1.0 */ public Email addReplyTo(final String email, final String name) throws EmailException { return addReplyTo(email, name, this.charset); } /** * Add a reply to address to the email using the specified address, * personal name, and charset encoding for the name. * * @param email A String. * @param name A String. * @param charset The charset to encode the name with. * @return An Email. * @throws EmailException Indicates an invalid email address or charset. * @since 1.1 */ public Email addReplyTo(final String email, final String name, final String charset) throws EmailException { this.replyList.add(createInternetAddress(email, name, charset)); return this; } /** * Set a list of reply to addresses. All elements in the specified * <code>Collection</code> are expected to be of type * <code>java.mail.internet.InternetAddress</code>. * * @param aCollection collection of <code>InternetAddress</code> objects * @return An Email. * @throws EmailException Indicates an invalid email address * @see javax.mail.internet.InternetAddress * @since 1.1 */ public Email setReplyTo(final Collection<InternetAddress> aCollection) throws EmailException { if (aCollection == null || aCollection.isEmpty()) { throw new EmailException("Address List provided was invalid"); } this.replyList = new ArrayList<InternetAddress>(aCollection); return this; } /** * Used to specify the mail headers. Example: * * X-Mailer: Sendmail, X-Priority: 1( highest ) * or 2( high ) 3( normal ) 4( low ) and 5( lowest ) * Disposition-Notification-To: user@domain.net * * @param map A Map. * @throws IllegalArgumentException if either of the provided header / value is null or empty * @since 1.0 */ public void setHeaders(final Map<String, String> map) { this.headers.clear(); for (final Map.Entry<String, String> entry : map.entrySet()) { addHeader(entry.getKey(), entry.getValue()); } } /** * Adds a header ( name, value ) to the headers Map. * * @param name A String with the name. * @param value A String with the value. * @since 1.0 * @throws IllegalArgumentException if either {@code name} or {@code value} is null or empty */ public void addHeader(final String name, final String value) { if (EmailUtils.isEmpty(name)) { throw new IllegalArgumentException("name can not be null or empty"); } if (EmailUtils.isEmpty(value)) { throw new IllegalArgumentException("value can not be null or empty"); } this.headers.put(name, value); } /** * Set the email subject. * * @param aSubject A String. * @return An Email. * @since 1.0 */ public Email setSubject(final String aSubject) { this.subject = aSubject; return this; } /** * Gets the "bounce address" of this email. * * @return the bounce address as string * @since 1.4 */ public String getBounceAddress() { return this.bounceAddress; } /** * Set the "bounce address" - the address to which undeliverable messages * will be returned. If this value is never set, then the message will be * sent to the address specified with the System property "mail.smtp.from", * or if that value is not set, then to the "from" address. * * @param email A String. * @return An Email. * @throws IllegalStateException when the mail session is already initialized * @since 1.0 */ public Email setBounceAddress(final String email) { checkSessionAlreadyInitialized(); this.bounceAddress = email; return this; } /** * Define the content of the mail. It should be overridden by the * subclasses. * * @param msg A String. * @return An Email. * @throws EmailException generic exception. * @since 1.0 */ public abstract Email setMsg(String msg) throws EmailException; /** * Does the work of actually building the MimeMessage. Please note that * a user rarely calls this method directly and only if he/she is * interested in the sending the underlying MimeMessage without * commons-email. * * @throws IllegalStateException if the MimeMessage was already built * @throws EmailException if there was an error. * @since 1.0 */ public void buildMimeMessage() throws EmailException { if (this.message != null) { // [EMAIL-95] we assume that an email is not reused therefore invoking // buildMimeMessage() more than once is illegal. throw new IllegalStateException("The MimeMessage is already built."); } try { this.message = this.createMimeMessage(this.getMailSession()); if (EmailUtils.isNotEmpty(this.subject)) { if (EmailUtils.isNotEmpty(this.charset)) { this.message.setSubject(this.subject, this.charset); } else { this.message.setSubject(this.subject); } } // update content type (and encoding) this.updateContentType(this.contentType); if (this.content != null) { if (EmailConstants.TEXT_PLAIN.equalsIgnoreCase(this.contentType) && this.content instanceof String) { // EMAIL-104: call explicitly setText to use default mime charset // (property "mail.mime.charset") in case none has been set this.message.setText(this.content.toString(), this.charset); } else { this.message.setContent(this.content, this.contentType); } } else if (this.emailBody != null) { if (this.contentType == null) { this.message.setContent(this.emailBody); } else { this.message.setContent(this.emailBody, this.contentType); } } else { this.message.setText(""); } if (this.fromAddress != null) { this.message.setFrom(this.fromAddress); } else { if (session.getProperty(EmailConstants.MAIL_SMTP_FROM) == null && session.getProperty(EmailConstants.MAIL_FROM) == null) { throw new EmailException("From address required"); } } if (this.toList.size() + this.ccList.size() + this.bccList.size() == 0) { throw new EmailException("At least one receiver address required"); } if (this.toList.size() > 0) { this.message.setRecipients(Message.RecipientType.TO, this.toInternetAddressArray(this.toList)); } if (this.ccList.size() > 0) { this.message.setRecipients(Message.RecipientType.CC, this.toInternetAddressArray(this.ccList)); } if (this.bccList.size() > 0) { this.message.setRecipients(Message.RecipientType.BCC, this.toInternetAddressArray(this.bccList)); } if (this.replyList.size() > 0) { this.message.setReplyTo(this.toInternetAddressArray(this.replyList)); } if (this.headers.size() > 0) { for (final Map.Entry<String, String> entry : this.headers.entrySet()) { final String foldedValue = createFoldedHeaderValue(entry.getKey(), entry.getValue()); this.message.addHeader(entry.getKey(), foldedValue); } } if (this.message.getSentDate() == null) { this.message.setSentDate(getSentDate()); } if (this.popBeforeSmtp) { final Store store = session.getStore("pop3"); store.connect(this.popHost, this.popUsername, this.popPassword); } } catch (final MessagingException me) { throw new EmailException(me); } } /** * Sends the previously created MimeMessage to the SMTP server. * * @return the message id of the underlying MimeMessage * @throws IllegalArgumentException if the MimeMessage has not been created * @throws EmailException the sending failed */ public String sendMimeMessage() throws EmailException { EmailUtils.notNull(this.message, "MimeMessage has not been created yet"); try { Transport.send(this.message); return this.message.getMessageID(); } catch (final Throwable t) { final String msg = "Sending the email to the following server failed : " + this.getHostName() + ":" + this.getSmtpPort(); throw new EmailException(msg, t); } } /** * Returns the internal MimeMessage. Please not that the * MimeMessage is build by the buildMimeMessage() method. * * @return the MimeMessage */ public MimeMessage getMimeMessage() { return this.message; } /** * Sends the email. Internally we build a MimeMessage * which is afterwards sent to the SMTP server. * * @return the message id of the underlying MimeMessage * @throws IllegalStateException if the MimeMessage was already built, ie {@link #buildMimeMessage()} * was already called * @throws EmailException the sending failed */ public String send() throws EmailException { this.buildMimeMessage(); return this.sendMimeMessage(); } /** * Sets the sent date for the email. The sent date will default to the * current date if not explicitly set. * * @param date Date to use as the sent date on the email * @since 1.0 */ public void setSentDate(final Date date) { if (date != null) { // create a separate instance to keep findbugs happy this.sentDate = new Date(date.getTime()); } } /** * Gets the sent date for the email. * * @return date to be used as the sent date for the email * @since 1.0 */ public Date getSentDate() { if (this.sentDate == null) { return new Date(); } return new Date(this.sentDate.getTime()); } /** * Gets the subject of the email. * * @return email subject */ public String getSubject() { return this.subject; } /** * Gets the sender of the email. * * @return from address */ public InternetAddress getFromAddress() { return this.fromAddress; } /** * Gets the host name of the SMTP server, * * @return host name */ public String getHostName() { if (this.session != null) { return this.session.getProperty(MAIL_HOST); } else if (EmailUtils.isNotEmpty(this.hostName)) { return this.hostName; } return null; } /** * Gets the listening port of the SMTP server. * * @return smtp port */ public String getSmtpPort() { if (this.session != null) { return this.session.getProperty(MAIL_PORT); } else if (EmailUtils.isNotEmpty(this.smtpPort)) { return this.smtpPort; } return null; } /** * Gets whether the client is configured to require STARTTLS. * * @return true if using STARTTLS for authentication, false otherwise * @since 1.3 */ public boolean isStartTLSRequired() { return this.startTlsRequired; } /** * Gets whether the client is configured to try to enable STARTTLS. * * @return true if using STARTTLS for authentication, false otherwise * @since 1.3 */ public boolean isStartTLSEnabled() { return this.startTlsEnabled || tls; } /** * Gets whether the client is configured to try to enable STARTTLS. * See EMAIL-105 for reason of deprecation. * * @deprecated since 1.3, use isStartTLSEnabled() instead * @return true if using STARTTLS for authentication, false otherwise * @since 1.1 */ @Deprecated public boolean isTLS() { return isStartTLSEnabled(); } /** * Utility to copy List of known InternetAddress objects into an * array. * * @param list A List. * @return An InternetAddress[]. * @since 1.0 */ protected InternetAddress[] toInternetAddressArray(final List<InternetAddress> list) { return list.toArray(new InternetAddress[list.size()]); } /** * Set details regarding "pop3 before smtp" authentication. * * @param newPopBeforeSmtp Whether or not to log into pop3 server before sending mail. * @param newPopHost The pop3 host to use. * @param newPopUsername The pop3 username. * @param newPopPassword The pop3 password. * @since 1.0 */ public void setPopBeforeSmtp(final boolean newPopBeforeSmtp, final String newPopHost, final String newPopUsername, final String newPopPassword) { this.popBeforeSmtp = newPopBeforeSmtp; this.popHost = newPopHost; this.popUsername = newPopUsername; this.popPassword = newPopPassword; } /** * Returns whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS). * See EMAIL-105 for reason of deprecation. * * @deprecated since 1.3, use isSSLOnConnect() instead * @return true if SSL enabled for the transport */ @Deprecated public boolean isSSL() { return isSSLOnConnect(); } /** * Returns whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS). * * @return true if SSL enabled for the transport * @since 1.3 */ public boolean isSSLOnConnect() { return sslOnConnect || ssl; } /** * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS). * See EMAIL-105 for reason of deprecation. * * @deprecated since 1.3, use setSSLOnConnect() instead * @param ssl whether to enable the SSL transport */ @Deprecated public void setSSL(final boolean ssl) { setSSLOnConnect(ssl); } /** * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS). * * @param ssl whether to enable the SSL transport * @return An Email. * @since 1.3 */ public Email setSSLOnConnect(final boolean ssl) { checkSessionAlreadyInitialized(); this.sslOnConnect = ssl; this.ssl = ssl; return this; } /** * Is the server identity checked as specified by RFC 2595 * * @return true if the server identity is checked * @since 1.3 */ public boolean isSSLCheckServerIdentity() { return sslCheckServerIdentity; } /** * Sets whether the server identity is checked as specified by RFC 2595 * * @param sslCheckServerIdentity whether to enable server identity check * @return An Email. * @since 1.3 */ public Email setSSLCheckServerIdentity(final boolean sslCheckServerIdentity) { checkSessionAlreadyInitialized(); this.sslCheckServerIdentity = sslCheckServerIdentity; return this; } /** * Returns the current SSL port used by the SMTP transport. * * @return the current SSL port used by the SMTP transport */ public String getSslSmtpPort() { if (this.session != null) { return this.session.getProperty(MAIL_SMTP_SOCKET_FACTORY_PORT); } else if (EmailUtils.isNotEmpty(this.sslSmtpPort)) { return this.sslSmtpPort; } return null; } /** * Sets the SSL port to use for the SMTP transport. Defaults to the standard * port, 465. * * @param sslSmtpPort the SSL port to use for the SMTP transport */ public void setSslSmtpPort(final String sslSmtpPort) { checkSessionAlreadyInitialized(); this.sslSmtpPort = sslSmtpPort; } /** * If partial sending of email enabled. * * @return true if sending partial email is enabled * @since 1.3.2 */ public boolean isSendPartial() { return sendPartial; } /** * Sets whether the email is partially send in case of invalid addresses. * <p> * In case the mail server rejects an address as invalid, the call to {@link #send()} * may throw a {@link javax.mail.SendFailedException}, even if partial send mode is enabled (emails * to valid addresses will be transmitted). In case the email server does not reject * invalid addresses immediately, but return a bounce message, no exception will be thrown * by the {@link #send()} method. * * @param sendPartial whether to enable partial send mode * @return An Email. * @since 1.3.2 */ public Email setSendPartial(final boolean sendPartial) { checkSessionAlreadyInitialized(); this.sendPartial = sendPartial; return this; } /** * Get the list of "To" addresses. * * @return List addresses */ public List<InternetAddress> getToAddresses() { return this.toList; } /** * Get the list of "CC" addresses. * * @return List addresses */ public List<InternetAddress> getCcAddresses() { return this.ccList; } /** * Get the list of "Bcc" addresses. * * @return List addresses */ public List<InternetAddress> getBccAddresses() { return this.bccList; } /** * Get the list of "Reply-To" addresses. * * @return List addresses */ public List<InternetAddress> getReplyToAddresses() { return this.replyList; } /** * Get the socket connection timeout value in milliseconds. * * @return the timeout in milliseconds. * @since 1.2 */ public int getSocketConnectionTimeout() { return this.socketConnectionTimeout; } /** * Set the socket connection timeout value in milliseconds. * Default is a 60 second timeout. * * @param socketConnectionTimeout the connection timeout * @since 1.2 */ public void setSocketConnectionTimeout(final int socketConnectionTimeout) { checkSessionAlreadyInitialized(); this.socketConnectionTimeout = socketConnectionTimeout; } /** * Get the socket I/O timeout value in milliseconds. * * @return the socket I/O timeout * @since 1.2 */ public int getSocketTimeout() { return this.socketTimeout; } /** * Set the socket I/O timeout value in milliseconds. * Default is 60 second timeout. * * @param socketTimeout the socket I/O timeout * @since 1.2 */ public void setSocketTimeout(final int socketTimeout) { checkSessionAlreadyInitialized(); this.socketTimeout = socketTimeout; } /** * Factory method to create a customized MimeMessage which can be * implemented by a derived class, e.g. to set the message id. * * @param aSession mail session to be used * @return the newly created message */ protected MimeMessage createMimeMessage(final Session aSession) { return new MimeMessage(aSession); } /** * Create a folded header value containing 76 character chunks. * * @param name the name of the header * @param value the value of the header * @return the folded header value * @throws IllegalArgumentException if either the name or value is null or empty */ private String createFoldedHeaderValue(final String name, final Object value) { String result; if (EmailUtils.isEmpty(name)) { throw new IllegalArgumentException("name can not be null or empty"); } if (value == null || EmailUtils.isEmpty(value.toString())) { throw new IllegalArgumentException("value can not be null or empty"); } try { result = MimeUtility.fold(name.length() + 2, MimeUtility.encodeText(value.toString(), this.charset, null)); } catch (final UnsupportedEncodingException e) { result = value.toString(); } return result; } /** * Creates a InternetAddress. * * @param email An email address. * @param name A name. * @param charsetName The name of the charset to encode the name with. * @return An internet address. * @throws EmailException Thrown when the supplied address, name or charset were invalid. */ private InternetAddress createInternetAddress(final String email, final String name, final String charsetName) throws EmailException { InternetAddress address = null; try { address = new InternetAddress(email); // check name input if (EmailUtils.isNotEmpty(name)) { // check charset input. if (EmailUtils.isEmpty(charsetName)) { address.setPersonal(name); } else { // canonicalize the charset name and make sure // the current platform supports it. final Charset set = Charset.forName(charsetName); address.setPersonal(name, set.name()); } } // run sanity check on new InternetAddress object; if this fails // it will throw AddressException. address.validate(); } catch (final AddressException e) { throw new EmailException(e); } catch (final UnsupportedEncodingException e) { throw new EmailException(e); } return address; } /** * When a mail session is already initialized setting the * session properties has no effect. In order to flag the * problem throw an IllegalStateException. * * @throws IllegalStateException when the mail session is already initialized */ private void checkSessionAlreadyInitialized() { if (this.session != null) { throw new IllegalStateException("The mail session is already initialized"); } } }