package org.simplejavamail.mailer; import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; import org.hazlewood.connor.bottema.emailaddress.EmailAddressValidator; import org.simplejavamail.MailException; import org.simplejavamail.email.Email; import org.simplejavamail.email.Recipient; import org.simplejavamail.mailer.config.ProxyConfig; import org.simplejavamail.mailer.config.ServerConfig; import org.simplejavamail.mailer.config.TransportStrategy; import org.simplejavamail.mailer.internal.mailsender.MailSender; import org.simplejavamail.converter.internal.mimemessage.MimeMessageHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.internet.MimeMessage; import java.util.EnumSet; import java.util.Properties; import static java.lang.String.format; import static org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria.RFC_COMPLIANT; import static org.simplejavamail.mailer.config.TransportStrategy.findStrategyForSession; import static org.simplejavamail.util.ConfigLoader.*; import static org.simplejavamail.util.ConfigLoader.Property.JAVAXMAIL_DEBUG; import static org.simplejavamail.util.ConfigLoader.Property.TRANSPORT_STRATEGY; /** * Mailing tool aimed for simplicity, for sending e-mails of any complexity. This includes e-mails with plain text and/or html content, embedded * images and separate attachments, SMTP, SMTPS / SSL and SMTP + SSL, custom Session object, DKIM domain signing and even authenticated SOCKS proxy * support and threaded batch processing. * <p> * This mailing tool abstracts the javax.mail API to a higher level easy to use API. This tool works with {@link Email} instances but can also convert * traditional {@link MimeMessage} objects to and from {@link Email} object. * <p> * The e-mail message structure is built to work with all e-mail clients and has been tested with many different webclients as well as some desktop * applications. * <p> * Technically, the resulting email structure is as follows:<br> * <p> * <pre> * - root * - related * - alternative * - mail text * - mail html text * - embedded images * - attachments * </pre> * <p/> * <br> Usage example:<br> * <p/> * <pre> * Email email = new Email(); * email.setFromAddress("lollypop", "lolly.pop@somemail.com"); * email.addRecipient("Sugar Cane", "sugar.cane@candystore.org", RecipientType.TO); * email.setText("We should meet up!!"); * email.setTextHTML("<b>We should meet up!</b>"); * email.setSubject("Hey"); * new Mailer(preconfiguredMailSession).sendMail(email); * // or: * new Mailer("smtp.someserver.com", 25, "username", "password").sendMail(email); * </pre> * <p> * <a href="http://www.simplejavamail.org">simplejavamail.org</a> * <p> * <hr/> * <p> * On a technical note, the {@link Mailer} class is the front facade for the public API. It limits itself to creating Session objects, offering * various constructors, sorting missing arguments using available properties and finally email validation. The actual sending and proxy configuration * is done by the internal {@link MailSender}. Some internal api is made public through this class for uses other than directly sending emails, such * as {@link #setDebug(boolean)} and {@link #signMessageWithDKIM(MimeMessage, Email)}. * * @author Benny Bottema * @see MimeMessageHelper.MimeEmailMessageWrapper * @see Email */ @SuppressWarnings("WeakerAccess") public class Mailer { private static final Logger LOGGER = LoggerFactory.getLogger(Mailer.class); private final MailSender mailSender; /** * Email address restriction flags set to {@link EmailAddressCriteria#RFC_COMPLIANT} or overridden by by user with {@link * #setEmailAddressCriteria(EnumSet)}. */ private EnumSet<EmailAddressCriteria> emailAddressCriteria = RFC_COMPLIANT; /** * Custom Session constructor, stores the given mail session for later use. Assumes that *all* properties used to make a connection are configured * (host, port, authentication and transport protocol settings). Will skip proxy. * * @param session A preconfigured mail {@link Session} object with which a {@link Message} can be produced. * @see #Mailer(Session, ProxyConfig) */ public Mailer(final Session session) { this(session, new ProxyConfig()); } /** * Custom Session constructor with proxy, stores the given mail session for later use. Assumes that *all* properties used to make a connection are * configured (host, port, authentication and transport protocol settings). * <p> * Only proxy settings are always added if details are provided. * <p> * Also set javax.mail debug mode if a config file was provided for this. * * @param session A preconfigured mail {@link Session} object with which a {@link Message} can be produced. * @param proxyConfig Remote proxy server details, if the connection should be run through a SOCKS proxy. */ @SuppressWarnings("SameParameterValue") public Mailer(final Session session, final ProxyConfig proxyConfig) { if (hasProperty(JAVAXMAIL_DEBUG)) { setDebug((Boolean) getProperty(JAVAXMAIL_DEBUG)); } this.mailSender = new MailSender(session, proxyConfig, findStrategyForSession(session)); } /** * No-arg constructor that only works with properly populated config file ("simplejavamail.properties") on the classpath. * <p> * Delegates to {@link #Mailer(ServerConfig, TransportStrategy, ProxyConfig)} and populates as much as possible from the config file (smtp server * details, proxy details, transport strategy) and otherwise defaults to {@link TransportStrategy#SMTP_PLAIN} and skipping proxy. * * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer() { this(new ServerConfig(null, null, null, null), null, null); } /** * @param host The address URL of the SMTP server to be used. * @param port The port of the SMTP server. * @param username An optional username, may be <code>null</code>. * @param password An optional password, may be <code>null</code>, but only if username is <code>null</code> as well. * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer(final String host, final Integer port, final String username, final String password) { this(new ServerConfig(host, port, username, password), null, null); } /** * @param serverConfig Remote SMTP server details. * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer(final ServerConfig serverConfig) { this(serverConfig, null, null); } /** * @param host The address URL of the SMTP server to be used. * @param port The port of the SMTP server. * @param username An optional username, may be <code>null</code>. * @param password An optional password, may be <code>null</code>, but only if username is <code>null</code> as well. * @param transportStrategy The transport protocol configuration type for handling SSL or TLS (or vanilla SMTP) * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer(final String host, final Integer port, final String username, final String password, final TransportStrategy transportStrategy) { this(new ServerConfig(host, port, username, password), transportStrategy, null); } /** * @param serverConfig Remote SMTP server details. * @param transportStrategy The transport protocol configuration type for handling SSL or TLS (or vanilla SMTP) * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer(final ServerConfig serverConfig, final TransportStrategy transportStrategy) { this(serverConfig, transportStrategy, null); } /** * @param serverConfig Remote SMTP server details. * @param proxyConfig Remote proxy server details, if the connection should be run through a SOCKS proxy. * @see #Mailer(ServerConfig, TransportStrategy, ProxyConfig) */ public Mailer(final ServerConfig serverConfig, final ProxyConfig proxyConfig) { this(serverConfig, null, proxyConfig); } /** * Main constructor which produces a new {@link Session} on the fly. Use this if you don't have a mail session configured in your web container, * or Spring context etc. * <p> * Also set javax.mail debug mode if a config file was provided for this. * * @param serverConfig Remote SMTP server details. * @param transportStrategy The transport protocol configuration type for handling SSL or TLS (or vanilla SMTP) * @param proxyConfig Remote proxy server details, if the connection should be run through a SOCKS proxy. */ public Mailer(final ServerConfig serverConfig, final TransportStrategy transportStrategy, final ProxyConfig proxyConfig) { final TransportStrategy effectiveTransportStrategy = valueOrProperty(transportStrategy, TRANSPORT_STRATEGY, TransportStrategy.SMTP_PLAIN); final Session session = createMailSession(serverConfig, effectiveTransportStrategy); this.mailSender = new MailSender(session, proxyConfig, effectiveTransportStrategy); this.emailAddressCriteria = null; if (hasProperty(JAVAXMAIL_DEBUG)) { setDebug((Boolean) getProperty(JAVAXMAIL_DEBUG)); } } /** * Instantiates and configures the {@link Session} instance. Delegates resolving transport protocol specific properties to the given {@link * TransportStrategy} in two ways: <ol> <li>request an initial property list which the strategy may pre-populate</li> <li>by requesting the * property names according to the respective transport protocol it handles (for the host property for example it would be * <em>"mail.smtp.host"</em> for SMTP and <em>"mail.smtps.host"</em> for SMTPS)</li> </ol> * <p> * Furthermore adds proxy SOCKS properties if a proxy configuration was provided, overwriting any SOCKS properties already present. * * @param serverConfig Remote SMTP server details. * @param transportStrategy The transport protocol strategy enum that actually handles the session configuration. Session configuration meaning * setting the right properties for the appropriate transport type (ie. <em>"mail.smtp.host"</em> for SMTP, * <em>"mail.smtps.host"</em> for SMTPS). * @return A fully configured <code>Session</code> instance complete with transport protocol settings. * @see TransportStrategy#generateProperties() * @see TransportStrategy#propertyNameHost() * @see TransportStrategy#propertyNamePort() * @see TransportStrategy#propertyNameUsername() * @see TransportStrategy#propertyNameAuthenticate() */ @SuppressWarnings("WeakerAccess") public static Session createMailSession(final ServerConfig serverConfig, final TransportStrategy transportStrategy) { final Properties props = transportStrategy.generateProperties(); props.put(transportStrategy.propertyNameHost(), serverConfig.getHost()); props.put(transportStrategy.propertyNamePort(), String.valueOf(serverConfig.getPort())); if (serverConfig.getUsername() != null) { props.put(transportStrategy.propertyNameUsername(), serverConfig.getUsername()); } if (serverConfig.getPassword() != null) { props.put(transportStrategy.propertyNameAuthenticate(), "true"); return Session.getInstance(props, new SmtpAuthenticator(serverConfig)); } else { return Session.getInstance(props); } } /** * In case Simple Java Mail falls short somehow, you can get a hold of the internal {@link Session} instance to debug or tweak. Please let us know * why you are needing this on https://github.com/bbottema/simple-java-mail/issues. */ public Session getSession() { LOGGER.warn("Providing access to Session instance for emergency fall-back scenario. Please let us know why you need it."); LOGGER.warn("\t>https://github.com/bbottema/simple-java-mail/issues"); return mailSender.getSession(); } /** * Calls {@link Session#setDebug(boolean)} so that it generates debug information. To get more information out of the underlying JavaMail * framework or out of Simple Java Mail, increase logging config of your chosen logging framework (examples <a * href="http://www.simplejavamail.org/#/proxy">here</a>). * * @param debug Flag to indicate debug mode yes/no. * @see Property#JAVAXMAIL_DEBUG */ public void setDebug(final boolean debug) { mailSender.setDebug(debug); } /** * Sets the transport mode for this mail sender to logging only, which means no mail will be actually sent out. * * @param transportModeLoggingOnly Flag to indicate logging mode yes/no. */ public synchronized void setTransportModeLoggingOnly(final boolean transportModeLoggingOnly) { mailSender.setTransportModeLoggingOnly(transportModeLoggingOnly); } /** * @return Whether this Mailer is set to only log or also actually send emails through an SMTP server (which is the default). */ public boolean isTransportModeLoggingOnly() { return mailSender.isTransportModeLoggingOnly(); } /** * Configures the current session to trust all hosts and don't validate any SSL keys. The property "mail.smtp.ssl.trust" is set to "*". * <p> * Refer to https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html#mail.smtp.ssl.trust */ public void trustAllSSLHosts(final boolean trustAllHosts) { mailSender.trustAllHosts(trustAllHosts); } /** * Configures the current session to white list all provided hosts and don't validate SSL keys for them. The property "mail.smtp.ssl.trust" is set * to a comma separated list. * <p> * Refer to https://javamail.java.net/nonav/docs/api/com/sun/mail/smtp/package-summary.html#mail.smtp.ssl.trust */ public void trustSSLHosts(final String... hosts) { mailSender.trustHosts(hosts); } /** * Copies all property entries into the {@link Session} using {@link Session#getProperties()}. * * @param properties The source properties to add or override in the internal {@link Session} instance. */ public void applyProperties(final Properties properties) { mailSender.applyProperties(properties); } /** * @param poolSize The maximum number of threads when sending emails in async fashion. * @see Property#DEFAULT_POOL_SIZE */ public void setThreadPoolSize(final int poolSize) { mailSender.setThreadPoolSize(poolSize); } /** * Delegates to {@link #sendMail(Email, boolean)}, with <code>async = false</code>. This method returns only when the email has been processed by * the target SMTP server. */ public final void sendMail(final Email email) { sendMail(email, false); } /** * @see MailSender#send(Email, boolean) * @see #validate(Email) */ public final synchronized void sendMail(final Email email, @SuppressWarnings("SameParameterValue") final boolean async) { if (validate(email)) { mailSender.send(email, async); } } /** * Validates an {@link Email} instance. Validation fails if the subject is missing, content is missing, or no recipients are defined. * * @param email The email that needs to be configured correctly. * @return Always <code>true</code> (throws a {@link MailException} exception if validation fails). * @throws MailException Is being thrown in any of the above causes. * @see EmailAddressValidator */ @SuppressWarnings({ "SameReturnValue", "WeakerAccess" }) public boolean validate(final Email email) throws MailException { if (email.getText() == null && email.getTextHTML() == null) { throw new MailerException(MailerException.MISSING_CONTENT); } else if (email.getSubject() == null || email.getSubject().equals("")) { throw new MailerException(MailerException.MISSING_SUBJECT); } else if (email.getRecipients().size() == 0) { throw new MailerException(MailerException.MISSING_RECIPIENT); } else if (email.getFromRecipient() == null) { throw new MailerException(MailerException.MISSING_SENDER); } else if (emailAddressCriteria != null) { if (!EmailAddressValidator.isValid(email.getFromRecipient().getAddress(), emailAddressCriteria)) { throw new MailerException(format(MailerException.INVALID_SENDER, email)); } for (final Recipient recipient : email.getRecipients()) { if (!EmailAddressValidator.isValid(recipient.getAddress(), emailAddressCriteria)) { throw new MailerException(format(MailerException.INVALID_RECIPIENT, email)); } } if (email.getReplyToRecipient() != null && !EmailAddressValidator .isValid(email.getReplyToRecipient().getAddress(), emailAddressCriteria)) { throw new MailerException(format(MailerException.INVALID_REPLYTO, email)); } } return true; } /** * Refer to {@link MimeMessageHelper#signMessageWithDKIM(MimeMessage, Email)} */ public static MimeMessage signMessageWithDKIM(final MimeMessage message, final Email email) { return MimeMessageHelper.signMessageWithDKIM(message, email); } /** * Overrides the default email address validation restrictions {@link #emailAddressCriteria} when validating and sending emails using the current * <code>Mailer</code> instance. */ public void setEmailAddressCriteria(final EnumSet<EmailAddressCriteria> emailAddressCriteria) { this.emailAddressCriteria = emailAddressCriteria; } /** * Simple Authenticator used to create a {@link Session} object with in {@link #createMailSession(ServerConfig, TransportStrategy)}. */ private static class SmtpAuthenticator extends Authenticator { private final ServerConfig serverConfig; public SmtpAuthenticator(final ServerConfig serverConfig) { this.serverConfig = serverConfig; } @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(serverConfig.getUsername(), serverConfig.getPassword()); } } }