View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.mail2.javax;
18  
19  import java.io.UnsupportedEncodingException;
20  import java.nio.charset.Charset;
21  import java.time.Duration;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Objects;
29  import java.util.Properties;
30  
31  import javax.mail.Authenticator;
32  import javax.mail.Message;
33  import javax.mail.MessagingException;
34  import javax.mail.Session;
35  import javax.mail.Store;
36  import javax.mail.Transport;
37  import javax.mail.internet.AddressException;
38  import javax.mail.internet.InternetAddress;
39  import javax.mail.internet.MimeMessage;
40  import javax.mail.internet.MimeMultipart;
41  import javax.mail.internet.MimeUtility;
42  import javax.naming.Context;
43  import javax.naming.InitialContext;
44  import javax.naming.NamingException;
45  
46  import org.apache.commons.mail2.core.EmailConstants;
47  import org.apache.commons.mail2.core.EmailException;
48  import org.apache.commons.mail2.core.EmailUtils;
49  import org.apache.commons.mail2.javax.util.IDNEmailAddressConverter;
50  
51  /**
52   * The abstract class for all email messages. This class sets the sender's email, name, receiver's email, name, subject, and send date.
53   * <p>
54   * Subclasses are responsible for setting the message body.
55   * </p>
56   *
57   * @since 1.0
58   */
59  public abstract class Email {
60  
61      /**
62       * Empty array.
63       */
64      private static final InternetAddress[] EMPTY_INTERNET_ADDRESS_ARRAY = {};
65  
66      /**
67       * The email message to send.
68       */
69      private MimeMessage message;
70  
71      /**
72       * The charset to use for this message.
73       */
74      private String charset;
75  
76      /**
77       * The Address of the sending party, mandatory.
78       */
79      private InternetAddress fromAddress;
80  
81      /**
82       * The Subject.
83       */
84      private String subject;
85  
86      /**
87       * An attachment.
88       */
89      private MimeMultipart emailBody;
90  
91      /**
92       * The content.
93       */
94      private Object content;
95  
96      /**
97       * The content type.
98       */
99      private String contentType;
100 
101     /**
102      * Set session debugging on or off.
103      */
104     private boolean debug;
105 
106     /**
107      * Sent date.
108      */
109     private Date sentDate;
110 
111     /**
112      * Instance of an {@code Authenticator} object that will be used when authentication is requested from the mail server.
113      */
114     private Authenticator authenticator;
115 
116     /**
117      * The hostname of the mail server with which to connect. If null will try to get property from system.properties. If still null, quit.
118      */
119     private String hostName;
120 
121     /**
122      * The port number of the mail server to connect to. Defaults to the standard port ( 25 ).
123      */
124     private String smtpPort = "25";
125 
126     /**
127      * The port number of the SSL enabled SMTP server; defaults to the standard port, 465.
128      */
129     private String sslSmtpPort = "465";
130 
131     /**
132      * List of "to" email addresses.
133      */
134     private List<InternetAddress> toList = new ArrayList<>();
135 
136     /**
137      * List of "cc" email addresses.
138      */
139     private List<InternetAddress> ccList = new ArrayList<>();
140 
141     /**
142      * List of "bcc" email addresses.
143      */
144     private List<InternetAddress> bccList = new ArrayList<>();
145 
146     /**
147      * List of "replyTo" email addresses.
148      */
149     private List<InternetAddress> replyList = new ArrayList<>();
150 
151     /**
152      * 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
153      * type {@code String} rather than {@code InternetAddress}.
154      */
155     private String bounceAddress;
156 
157     /**
158      * Used to specify the mail headers. Example:
159      *
160      * X-Mailer: Sendmail, X-Priority: 1( highest ) or 2( high ) 3( normal ) 4( low ) and 5( lowest ) Disposition-Notification-To: user@domain.net
161      */
162     private final Map<String, String> headers = new HashMap<>();
163 
164     /**
165      * Whether to use POP3 before SMTP, and if so the settings.
166      */
167     private boolean popBeforeSmtp;
168 
169     /**
170      * The host name of the POP3 server.
171      */
172     private String popHost;
173 
174     /**
175      * The user name to log into the POP3 server.
176      */
177     private String popUsername;
178 
179     /**
180      * The password to log into the POP3 server.
181      */
182     private String popPassword;
183 
184     /**
185      * Does server require TLS encryption for authentication?
186      */
187     private boolean tls;
188 
189     /**
190      * Does the current transport use SSL/TLS encryption upon connection?
191      */
192     private boolean ssl;
193 
194     /**
195      * Socket I/O timeout value in milliseconds.
196      */
197     private int socketTimeout = Math.toIntExact(EmailConstants.SOCKET_TIMEOUT.toMillis());
198 
199     /**
200      * Socket connection timeout value in milliseconds.
201      */
202     private int socketConnectionTimeout = Math.toIntExact(EmailConstants.SOCKET_TIMEOUT.toMillis());
203 
204     /**
205      * 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
206      * login commands. Note that an appropriate trust store must configured so that the client will trust the server's certificate. Defaults to false.
207      */
208     private boolean startTlsEnabled;
209 
210     /**
211      * 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
212      * fail. Defaults to false.
213      */
214     private boolean startTlsRequired;
215 
216     /**
217      * Does the current transport use SSL/TLS encryption upon connection?
218      */
219     private boolean sslOnConnect;
220 
221     /**
222      * 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
223      * to prevent man-in-the-middle attacks. Defaults to false.
224      */
225     private boolean sslCheckServerIdentity;
226 
227     /**
228      * If set to true, and a message has some valid and some invalid addresses, send the message anyway, reporting the partial failure with a
229      * 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
230      * to false.
231      */
232     private boolean sendPartial;
233 
234     /**
235      * The Session to mail with.
236      */
237     private Session session;
238 
239     /**
240      * Constructs a new instance.
241      */
242     public Email() {
243         // empty
244     }
245 
246     /**
247      * Adds 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
248      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
249      * otherwise, it is used as is.
250      *
251      * @param email A String.
252      * @return An Email.
253      * @throws EmailException Indicates an invalid email address
254      * @since 1.0
255      */
256     public Email addBcc(final String email) throws EmailException {
257         return addBcc(email, null);
258     }
259 
260     /**
261      * Adds 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
262      * of {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII
263      * characters; otherwise, it is used as is.
264      *
265      * @param emails A String array.
266      * @return An Email.
267      * @throws EmailException Indicates an invalid email address
268      * @since 1.3
269      */
270     public Email addBcc(final String... emails) throws EmailException {
271         EmailException.checkNonEmpty(emails, () -> "BCC list invalid.");
272         for (final String email : emails) {
273             addBcc(email, null);
274         }
275         return this;
276     }
277 
278     /**
279      * Adds 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
280      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
281      * otherwise, it is used as is.
282      *
283      * @param email A String.
284      * @param name  A String.
285      * @return An Email.
286      * @throws EmailException Indicates an invalid email address
287      * @since 1.0
288      */
289     public Email addBcc(final String email, final String name) throws EmailException {
290         return addBcc(email, name, charset);
291     }
292 
293     /**
294      * Adds a blind BCC recipient to the email using the specified address, personal name, and charset encoding for the name.
295      *
296      * @param email   A String.
297      * @param name    A String.
298      * @param charset The charset to encode the name with.
299      * @return An Email.
300      * @throws EmailException Indicates an invalid email address
301      * @since 1.1
302      */
303     public Email addBcc(final String email, final String name, final String charset) throws EmailException {
304         bccList.add(createInternetAddress(email, name, charset));
305         return this;
306     }
307 
308     /**
309      * Adds 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
310      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
311      * otherwise, it is used as is.
312      *
313      * @param email A String.
314      * @return An Email.
315      * @throws EmailException Indicates an invalid email address.
316      * @since 1.0
317      */
318     public Email addCc(final String email) throws EmailException {
319         return addCc(email, null);
320     }
321 
322     /**
323      * Adds 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
324      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
325      * otherwise, it is used as is.
326      *
327      * @param emails A String array.
328      * @return An Email.
329      * @throws EmailException Indicates an invalid email address.
330      * @since 1.3
331      */
332     public Email addCc(final String... emails) throws EmailException {
333         EmailException.checkNonEmpty(emails, () -> "CC list invalid.");
334         for (final String email : emails) {
335             addCc(email, null);
336         }
337         return this;
338     }
339 
340     /**
341      * Adds a recipient CC to the email using the specified address and the specified personal name. The name will be encoded by the charset of
342      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
343      * otherwise, it is used as is.
344      *
345      * @param email A String.
346      * @param name  A String.
347      * @return An Email.
348      * @throws EmailException Indicates an invalid email address.
349      * @since 1.0
350      */
351     public Email addCc(final String email, final String name) throws EmailException {
352         return addCc(email, name, charset);
353     }
354 
355     /**
356      * Adds a recipient CC to the email using the specified address, personal name, and charset encoding for the name.
357      *
358      * @param email   A String.
359      * @param name    A String.
360      * @param charset The charset to encode the name with.
361      * @return An Email.
362      * @throws EmailException Indicates an invalid email address or charset.
363      * @since 1.1
364      */
365     public Email addCc(final String email, final String name, final String charset) throws EmailException {
366         ccList.add(createInternetAddress(email, name, charset));
367         return this;
368     }
369 
370     /**
371      * Adds a header ( name, value ) to the headers Map.
372      *
373      * @param name  A String with the name.
374      * @param value A String with the value.
375      * @since 1.0
376      * @throws IllegalArgumentException if either {@code name} or {@code value} is null or empty
377      */
378     public void addHeader(final String name, final String value) {
379         if (EmailUtils.isEmpty(name)) {
380             throw new IllegalArgumentException("name can not be null or empty");
381         }
382         if (EmailUtils.isEmpty(value)) {
383             throw new IllegalArgumentException("value can not be null or empty");
384         }
385         headers.put(name, value);
386     }
387 
388     /**
389      * Adds 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
390      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
391      * otherwise, it is used as is.
392      *
393      * @param email A String.
394      * @return An Email.
395      * @throws EmailException Indicates an invalid email address
396      * @since 1.0
397      */
398     public Email addReplyTo(final String email) throws EmailException {
399         return addReplyTo(email, null);
400     }
401 
402     /**
403      * Adds 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
404      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
405      * otherwise, it is used as is.
406      *
407      * @param email A String.
408      * @param name  A String.
409      * @return An Email.
410      * @throws EmailException Indicates an invalid email address
411      * @since 1.0
412      */
413     public Email addReplyTo(final String email, final String name) throws EmailException {
414         return addReplyTo(email, name, charset);
415     }
416 
417     /**
418      * Adds a reply to address to the email using the specified address, personal name, and charset encoding for the name.
419      *
420      * @param email   A String.
421      * @param name    A String.
422      * @param charset The charset to encode the name with.
423      * @return An Email.
424      * @throws EmailException Indicates an invalid email address or charset.
425      * @since 1.1
426      */
427     public Email addReplyTo(final String email, final String name, final String charset) throws EmailException {
428         replyList.add(createInternetAddress(email, name, charset));
429         return this;
430     }
431 
432     /**
433      * Adds 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
434      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
435      * otherwise, it is used as is.
436      *
437      * @param email A String.
438      * @return An Email.
439      * @throws EmailException Indicates an invalid email address.
440      * @since 1.0
441      */
442     public Email addTo(final String email) throws EmailException {
443         return addTo(email, null);
444     }
445 
446     /**
447      * Adds 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
448      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
449      * otherwise, it is used as is.
450      *
451      * @param emails A String array.
452      * @return An Email.
453      * @throws EmailException Indicates an invalid email address.
454      * @since 1.3
455      */
456     public Email addTo(final String... emails) throws EmailException {
457         EmailException.checkNonEmpty(emails, () -> "To list invalid.");
458         for (final String email : emails) {
459             addTo(email, null);
460         }
461         return this;
462     }
463 
464     /**
465      * Adds a recipient TO to the email using the specified address and the specified personal name. The name will be encoded by the charset of
466      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
467      * otherwise, it is used as is.
468      *
469      * @param email A String.
470      * @param name  A String.
471      * @return An Email.
472      * @throws EmailException Indicates an invalid email address.
473      * @since 1.0
474      */
475     public Email addTo(final String email, final String name) throws EmailException {
476         return addTo(email, name, charset);
477     }
478 
479     /**
480      * Adds a recipient TO to the email using the specified address, personal name, and charset encoding for the name.
481      *
482      * @param email   A String.
483      * @param name    A String.
484      * @param charset The charset to encode the name with.
485      * @return An Email.
486      * @throws EmailException Indicates an invalid email address or charset.
487      * @since 1.1
488      */
489     public Email addTo(final String email, final String name, final String charset) throws EmailException {
490         toList.add(createInternetAddress(email, name, charset));
491         return this;
492     }
493 
494     /**
495      * Builds the MimeMessage. Please note that a user rarely calls this method directly and only if he/she is interested in the sending the underlying
496      * MimeMessage without commons-email.
497      *
498      * @throws IllegalStateException if the MimeMessage was already built
499      * @throws EmailException        if there was an error.
500      * @since 1.0
501      */
502     public void buildMimeMessage() throws EmailException {
503         if (message != null) {
504             // [EMAIL-95] we assume that an email is not reused therefore invoking
505             // buildMimeMessage() more than once is illegal.
506             throw new IllegalStateException("The MimeMessage is already built.");
507         }
508 
509         try {
510             message = createMimeMessage(getMailSession());
511 
512             if (EmailUtils.isNotEmpty(subject)) {
513                 if (EmailUtils.isNotEmpty(charset)) {
514                     message.setSubject(subject, charset);
515                 } else {
516                     message.setSubject(subject);
517                 }
518             }
519 
520             // update content type (and encoding)
521             updateContentType(contentType);
522 
523             if (content != null) {
524                 if (EmailConstants.TEXT_PLAIN.equalsIgnoreCase(contentType) && content instanceof String) {
525                     // EMAIL-104: call explicitly setText to use default mime charset
526                     // (property "mail.mime.charset") in case none has been set
527                     message.setText(content.toString(), charset);
528                 } else {
529                     message.setContent(content, contentType);
530                 }
531             } else if (emailBody != null) {
532                 if (contentType == null) {
533                     message.setContent(emailBody);
534                 } else {
535                     message.setContent(emailBody, contentType);
536                 }
537             } else {
538                 message.setText("");
539             }
540 
541             if (fromAddress != null) {
542                 message.setFrom(fromAddress);
543             } else if (session.getProperty(EmailConstants.MAIL_SMTP_FROM) == null && session.getProperty(EmailConstants.MAIL_FROM) == null) {
544                 throw new EmailException("From address required");
545             }
546 
547             if (toList.size() + ccList.size() + bccList.size() == 0) {
548                 throw new EmailException("At least one receiver address required");
549             }
550 
551             if (!EmailUtils.isEmpty(toList)) {
552                 message.setRecipients(Message.RecipientType.TO, toInternetAddressArray(toList));
553             }
554 
555             if (!EmailUtils.isEmpty(ccList)) {
556                 message.setRecipients(Message.RecipientType.CC, toInternetAddressArray(ccList));
557             }
558 
559             if (!EmailUtils.isEmpty(bccList)) {
560                 message.setRecipients(Message.RecipientType.BCC, toInternetAddressArray(bccList));
561             }
562 
563             if (!EmailUtils.isEmpty(replyList)) {
564                 message.setReplyTo(toInternetAddressArray(replyList));
565             }
566 
567             if (!EmailUtils.isEmpty(headers)) {
568                 for (final Map.Entry<String, String> entry : headers.entrySet()) {
569                     final String foldedValue = createFoldedHeaderValue(entry.getKey(), entry.getValue());
570                     message.addHeader(entry.getKey(), foldedValue);
571                 }
572             }
573 
574             if (message.getSentDate() == null) {
575                 message.setSentDate(getSentDate());
576             }
577 
578             if (popBeforeSmtp) {
579                 // TODO Why is this not a Store leak? When to close?
580                 final Store store = session.getStore("pop3");
581                 store.connect(popHost, popUsername, popPassword);
582             }
583         } catch (final MessagingException e) {
584             throw new EmailException(e);
585         }
586     }
587 
588     /**
589      * When a mail session is already initialized setting the session properties has no effect. In order to flag the problem throw an IllegalStateException.
590      *
591      * @throws IllegalStateException when the mail session is already initialized
592      */
593     private void checkSessionAlreadyInitialized() {
594         if (session != null) {
595             throw new IllegalStateException("The mail session is already initialized");
596         }
597     }
598 
599     /**
600      * Creates a folded header value containing 76 character chunks.
601      *
602      * @param name  the name of the header
603      * @param value the value of the header
604      * @return the folded header value
605      * @throws IllegalArgumentException if either the name or value is null or empty
606      */
607     private String createFoldedHeaderValue(final String name, final String value) {
608         if (EmailUtils.isEmpty(name)) {
609             throw new IllegalArgumentException("name can not be null or empty");
610         }
611         if (EmailUtils.isEmpty(value)) {
612             throw new IllegalArgumentException("value can not be null or empty");
613         }
614         try {
615             return MimeUtility.fold(name.length() + 2, MimeUtility.encodeText(value, charset, null));
616         } catch (final UnsupportedEncodingException e) {
617             return value;
618         }
619     }
620 
621     /**
622      * Creates an InternetAddress.
623      *
624      * @param email       An email address.
625      * @param name        A name.
626      * @param charsetName The name of the charset to encode the name with.
627      * @return An internet address.
628      * @throws EmailException Thrown when the supplied address, name or charset were invalid.
629      */
630     private InternetAddress createInternetAddress(final String email, final String name, final String charsetName) throws EmailException {
631         try {
632             final InternetAddress address = new InternetAddress(new IDNEmailAddressConverter().toASCII(email));
633             // check name input
634             if (EmailUtils.isNotEmpty(name)) {
635                 // check charset input.
636                 if (EmailUtils.isEmpty(charsetName)) {
637                     address.setPersonal(name);
638                 } else {
639                     // canonicalize the charset name and make sure
640                     // the current platform supports it.
641                     final Charset set = Charset.forName(charsetName);
642                     address.setPersonal(name, set.name());
643                 }
644             }
645             // run sanity check on new InternetAddress object; if this fails
646             // it will throw AddressException.
647             address.validate();
648             return address;
649         } catch (final AddressException | UnsupportedEncodingException e) {
650             throw new EmailException(e);
651         }
652     }
653 
654     /**
655      * Creates a customized MimeMessage which can be implemented by a derived class, e.g. to set the message id.
656      *
657      * @param aSession mail session to be used
658      * @return the newly created message
659      */
660     protected MimeMessage createMimeMessage(final Session aSession) {
661         return new MimeMessage(aSession);
662     }
663 
664     /**
665      * Gets the authenticator.
666      *
667      * @return the authenticator.
668      * @since 1.6.0
669      */
670     public Authenticator getAuthenticator() {
671         return authenticator;
672     }
673 
674     /**
675      * Gets the list of "Bcc" addresses.
676      *
677      * @return List addresses
678      */
679     public List<InternetAddress> getBccAddresses() {
680         return bccList;
681     }
682 
683     /**
684      * Gets the "bounce address" of this email.
685      *
686      * @return the bounce address as string
687      * @since 1.4
688      */
689     public String getBounceAddress() {
690         return bounceAddress;
691     }
692 
693     /**
694      * Gets the list of "CC" addresses.
695      *
696      * @return List addresses
697      */
698     public List<InternetAddress> getCcAddresses() {
699         return ccList;
700     }
701 
702     /**
703      * Gets the Charset.
704      *
705      * @return the Charset.
706      * @since 1.6.0
707      */
708     public String getCharsetName() {
709         return charset;
710     }
711 
712     /**
713      * Gets the content.
714      *
715      * @return the content.
716      * @since 1.6.0
717      */
718     public Object getContent() {
719         return content;
720     }
721 
722     /**
723      * Gets the content type.
724      *
725      * @return the content type.
726      * @since 1.6.0
727      */
728     public String getContentType() {
729         return contentType;
730     }
731 
732     /**
733      * Gets the email body.
734      *
735      * @return the email body.
736      * @since 1.6.0
737      */
738     public MimeMultipart getEmailBody() {
739         return emailBody;
740     }
741 
742     /**
743      * Gets the sender of the email.
744      *
745      * @return from address
746      */
747     public InternetAddress getFromAddress() {
748         return fromAddress;
749     }
750 
751     /**
752      * Gets the specified header.
753      *
754      * @param header A string with the header.
755      * @return The value of the header, or null if no such header.
756      * @since 1.5
757      */
758     public String getHeader(final String header) {
759         return headers.get(header);
760     }
761 
762     /**
763      * Gets all headers on an Email.
764      *
765      * @return a Map of all headers.
766      * @since 1.5
767      */
768     public Map<String, String> getHeaders() {
769         return headers;
770     }
771 
772     /**
773      * Gets the host name of the SMTP server,
774      *
775      * @return host name
776      */
777     public String getHostName() {
778         if (session != null) {
779             return session.getProperty(EmailConstants.MAIL_HOST);
780         }
781         if (EmailUtils.isNotEmpty(hostName)) {
782             return hostName;
783         }
784         return null;
785     }
786 
787     /**
788      * Gets the mail session used when sending this Email, creating the Session if necessary. When a mail session is already initialized setting the session
789      * related properties will cause an IllegalStateException.
790      *
791      * @return A Session.
792      * @throws EmailException if the host name was not set
793      * @since 1.0
794      */
795     public Session getMailSession() throws EmailException {
796         if (session == null) {
797             final Properties properties = new Properties(System.getProperties());
798             properties.setProperty(EmailConstants.MAIL_TRANSPORT_PROTOCOL, EmailConstants.SMTP);
799 
800             if (EmailUtils.isEmpty(hostName)) {
801                 hostName = properties.getProperty(EmailConstants.MAIL_HOST);
802             }
803 
804             EmailException.checkNonEmpty(hostName, () -> "Cannot find valid hostname for mail session");
805 
806             properties.setProperty(EmailConstants.MAIL_PORT, smtpPort);
807             properties.setProperty(EmailConstants.MAIL_HOST, hostName);
808             properties.setProperty(EmailConstants.MAIL_DEBUG, String.valueOf(debug));
809 
810             properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE, Boolean.toString(isStartTLSEnabled()));
811             properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_REQUIRED, Boolean.toString(isStartTLSRequired()));
812 
813             properties.setProperty(EmailConstants.MAIL_SMTP_SEND_PARTIAL, Boolean.toString(isSendPartial()));
814             properties.setProperty(EmailConstants.MAIL_SMTPS_SEND_PARTIAL, Boolean.toString(isSendPartial()));
815 
816             if (authenticator != null) {
817                 properties.setProperty(EmailConstants.MAIL_SMTP_AUTH, "true");
818             }
819 
820             if (isSSLOnConnect()) {
821                 properties.setProperty(EmailConstants.MAIL_PORT, sslSmtpPort);
822                 properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT, sslSmtpPort);
823                 properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
824                 properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false");
825             }
826 
827             if ((isSSLOnConnect() || isStartTLSEnabled()) && isSSLCheckServerIdentity()) {
828                 properties.setProperty(EmailConstants.MAIL_SMTP_SSL_CHECKSERVERIDENTITY, "true");
829             }
830 
831             if (bounceAddress != null) {
832                 properties.setProperty(EmailConstants.MAIL_SMTP_FROM, bounceAddress);
833             }
834 
835             if (socketTimeout > 0) {
836                 properties.setProperty(EmailConstants.MAIL_SMTP_TIMEOUT, Integer.toString(socketTimeout));
837             }
838 
839             if (socketConnectionTimeout > 0) {
840                 properties.setProperty(EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT, Integer.toString(socketConnectionTimeout));
841             }
842 
843             // changed this (back) to getInstance due to security exceptions
844             // caused when testing using Maven
845             session = Session.getInstance(properties, authenticator);
846         }
847         return session;
848     }
849 
850     /**
851      * Gets the message.
852      *
853      * @return the message.
854      * @since 1.6.0
855      */
856     public MimeMessage getMessage() {
857         return message;
858     }
859 
860     /**
861      * Gets the internal MimeMessage. Please note that the MimeMessage is built by the buildMimeMessage() method.
862      *
863      * @return the MimeMessage
864      */
865     public MimeMessage getMimeMessage() {
866         return message;
867     }
868 
869     /**
870      * Gets the POP3 host.
871      *
872      * @return the POP3 host.
873      * @since 1.6.0
874      */
875     public String getPopHost() {
876         return popHost;
877     }
878 
879     /**
880      * Gets the POP3 password.
881      *
882      * @return the POP3 password.
883      * @since 1.6.0
884      */
885     public String getPopPassword() {
886         return popPassword;
887     }
888 
889     /**
890      * Gets the POP3 user name.
891      *
892      * @return the POP3 user name.
893      * @since 1.6.0
894      */
895     public String getPopUserName() {
896         return popUsername;
897     }
898 
899     /**
900      * Gets the list of "Reply-To" addresses.
901      *
902      * @return List addresses
903      */
904     public List<InternetAddress> getReplyToAddresses() {
905         return replyList;
906     }
907 
908     /**
909      * Gets the sent date for the email.
910      *
911      * @return date to be used as the sent date for the email
912      * @since 1.0
913      */
914     public Date getSentDate() {
915         if (sentDate == null) {
916             return new Date();
917         }
918         return new Date(sentDate.getTime());
919     }
920 
921     /**
922      * Gets the listening port of the SMTP server.
923      *
924      * @return SMTP port
925      */
926     public String getSmtpPort() {
927         if (session != null) {
928             return session.getProperty(EmailConstants.MAIL_PORT);
929         }
930         if (EmailUtils.isNotEmpty(smtpPort)) {
931             return smtpPort;
932         }
933         return null;
934     }
935 
936     /**
937      * Gets the socket connection timeout value in milliseconds.
938      *
939      * @return the timeout in milliseconds.
940      * @since 1.2
941      */
942     public int getSocketConnectionTimeout() {
943         return socketConnectionTimeout;
944     }
945 
946     /**
947      * Gets the socket I/O timeout value in milliseconds.
948      *
949      * @return the socket I/O timeout
950      * @since 1.2
951      */
952     public int getSocketTimeout() {
953         return socketTimeout;
954     }
955 
956     /**
957      * Gets the current SSL port used by the SMTP transport.
958      *
959      * @return the current SSL port used by the SMTP transport
960      */
961     public String getSslSmtpPort() {
962         if (session != null) {
963             return session.getProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT);
964         }
965         if (EmailUtils.isNotEmpty(sslSmtpPort)) {
966             return sslSmtpPort;
967         }
968         return null;
969     }
970 
971     /**
972      * Gets the subject of the email.
973      *
974      * @return email subject
975      */
976     public String getSubject() {
977         return subject;
978     }
979 
980     /**
981      * Gets the list of "To" addresses.
982      *
983      * @return List addresses
984      */
985     public List<InternetAddress> getToAddresses() {
986         return toList;
987     }
988 
989     /**
990      * Tests whether debug is on.
991      *
992      * @return whether debug is on.
993      * @since 1.6.0
994      */
995     public boolean isDebug() {
996         return debug;
997     }
998 
999     /**
1000      * Tests whether to use POP3 before SMTP, and if so the settings.
1001      *
1002      * @return whether to use POP3 before SMTP, and if so the settings.
1003      * @since 1.6.0
1004      */
1005     public boolean isPopBeforeSmtp() {
1006         return popBeforeSmtp;
1007     }
1008 
1009     /**
1010      * Tests whether partial sending of email is enabled.
1011      *
1012      * @return true if sending partial email is enabled.
1013      * @since 1.3.2
1014      */
1015     public boolean isSendPartial() {
1016         return sendPartial;
1017     }
1018 
1019     /**
1020      * Tests whether the server identity checked as specified by RFC 2595
1021      *
1022      * @return true if the server identity is checked.
1023      * @since 1.3
1024      */
1025     public boolean isSSLCheckServerIdentity() {
1026         return sslCheckServerIdentity;
1027     }
1028 
1029     /**
1030      * Tests whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS).
1031      *
1032      * @return true if SSL enabled for the transport.
1033      * @since 1.3
1034      */
1035     public boolean isSSLOnConnect() {
1036         return sslOnConnect || ssl;
1037     }
1038 
1039     /**
1040      * Tests whether the client is configured to try to enable STARTTLS.
1041      *
1042      * @return true if using STARTTLS for authentication, false otherwise.
1043      * @since 1.3
1044      */
1045     public boolean isStartTLSEnabled() {
1046         return startTlsEnabled || tls;
1047     }
1048 
1049     /**
1050      * Tests whether the client is configured to require STARTTLS.
1051      *
1052      * @return true if using STARTTLS for authentication, false otherwise.
1053      * @since 1.3
1054      */
1055     public boolean isStartTLSRequired() {
1056         return startTlsRequired;
1057     }
1058 
1059     /**
1060      * Sends the email. Internally we build a MimeMessage which is afterwards sent to the SMTP server.
1061      *
1062      * @return the message id of the underlying MimeMessage
1063      * @throws IllegalStateException if the MimeMessage was already built, that is, {@link #buildMimeMessage()} was already called
1064      * @throws EmailException        the sending failed
1065      */
1066     public String send() throws EmailException {
1067         buildMimeMessage();
1068         return sendMimeMessage();
1069     }
1070 
1071     /**
1072      * Sends the previously created MimeMessage to the SMTP server.
1073      *
1074      * @return the message id of the underlying MimeMessage
1075      * @throws IllegalArgumentException if the MimeMessage has not been created
1076      * @throws EmailException           the sending failed
1077      */
1078     public String sendMimeMessage() throws EmailException {
1079         Objects.requireNonNull(message, "MimeMessage has not been created yet");
1080         try {
1081             Transport.send(message);
1082             return message.getMessageID();
1083         } catch (final Throwable t) {
1084             throw new EmailException("Sending the email to the following server failed : " + this.getHostName() + ":" + getSmtpPort(), t);
1085         }
1086     }
1087 
1088     /**
1089      * Sets the userName and password if authentication is needed. If this method is not used, no authentication will be performed.
1090      * <p>
1091      * This method will create a new instance of {@code DefaultAuthenticator} using the supplied parameters.
1092      * </p>
1093      *
1094      * @param userName User name for the SMTP server
1095      * @param password password for the SMTP server
1096      * @see DefaultAuthenticator
1097      * @see #setAuthenticator
1098      * @since 1.0
1099      */
1100     public void setAuthentication(final String userName, final String password) {
1101         this.setAuthenticator(new DefaultAuthenticator(userName, password));
1102     }
1103 
1104     /**
1105      * Sets the {@code Authenticator} to be used when authentication is requested from the mail server.
1106      * <p>
1107      * This method should be used when your outgoing mail server requires authentication. Your mail server must also support RFC2554.
1108      * </p>
1109      *
1110      * @param authenticator the {@code Authenticator} object.
1111      * @see Authenticator
1112      * @since 1.0
1113      */
1114     public void setAuthenticator(final Authenticator authenticator) {
1115         this.authenticator = authenticator;
1116     }
1117 
1118     /**
1119      * Sets a list of "BCC" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
1120      *
1121      * @param collection collection of {@code InternetAddress} objects
1122      * @return An Email.
1123      * @throws EmailException Indicates an invalid email address
1124      * @see javax.mail.internet.InternetAddress
1125      * @since 1.0
1126      */
1127     public Email setBcc(final Collection<InternetAddress> collection) throws EmailException {
1128         EmailException.checkNonEmpty(collection, () -> "BCC list invalid");
1129         bccList = new ArrayList<>(collection);
1130         return this;
1131     }
1132 
1133     /**
1134      * Sets 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
1135      * the address specified with the System property "mail.smtp.from", or if that value is not set, then to the "from" address.
1136      *
1137      * @param email A String.
1138      * @return An Email.
1139      * @throws IllegalStateException if the mail session is already initialized
1140      * @since 1.0
1141      */
1142     public Email setBounceAddress(final String email) {
1143         checkSessionAlreadyInitialized();
1144         if (!EmailUtils.isEmpty(email)) {
1145             try {
1146                 bounceAddress = createInternetAddress(email, null, charset).getAddress();
1147             } catch (final EmailException e) {
1148                 // Can't throw 'EmailException' to keep backward-compatibility
1149                 throw new IllegalArgumentException("Failed to set the bounce address : " + email, e);
1150             }
1151         } else {
1152             bounceAddress = email;
1153         }
1154 
1155         return this;
1156     }
1157 
1158     /**
1159      * Sets a list of "CC" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
1160      *
1161      * @param collection collection of {@code InternetAddress} objects.
1162      * @return An Email.
1163      * @throws EmailException Indicates an invalid email address.
1164      * @see javax.mail.internet.InternetAddress
1165      * @since 1.0
1166      */
1167     public Email setCc(final Collection<InternetAddress> collection) throws EmailException {
1168         EmailException.checkNonEmpty(collection, () -> "CC list invalid");
1169         ccList = new ArrayList<>(collection);
1170         return this;
1171     }
1172 
1173     /**
1174      * Sets the charset of the message. Please note that you should set the charset before adding the message content.
1175      *
1176      * @param charset A String.
1177      * @throws java.nio.charset.IllegalCharsetNameException if the charset name is invalid
1178      * @throws java.nio.charset.UnsupportedCharsetException if no support for the named charset exists in the current JVM
1179      * @since 1.0
1180      */
1181     public void setCharset(final String charset) {
1182         final Charset set = Charset.forName(charset);
1183         this.charset = set.name();
1184     }
1185 
1186     /**
1187      * Sets the emailBody to a MimeMultiPart
1188      *
1189      * @param mimeMultipart aMimeMultipart
1190      * @since 1.0
1191      */
1192     public void setContent(final MimeMultipart mimeMultipart) {
1193         this.emailBody = mimeMultipart;
1194     }
1195 
1196     /**
1197      * Sets the content.
1198      *
1199      * @param content the content.
1200      * @return {@code this} instance.
1201      * @since 1.6.0
1202      */
1203     public Email setContent(final Object content) {
1204         this.content = content;
1205         return this;
1206     }
1207 
1208     /**
1209      * Sets the content and contentType.
1210      *
1211      * @param content     content.
1212      * @param contentType content type.
1213      * @since 1.0
1214      */
1215     public void setContent(final Object content, final String contentType) {
1216         this.content = content;
1217         updateContentType(contentType);
1218     }
1219 
1220     /**
1221      * Sets the content type.
1222      *
1223      * @param contentType the content type.
1224      * @return {@code this} instance.
1225      * @since 1.6.0
1226      */
1227     public Email setContentType(final String contentType) {
1228         this.contentType = contentType;
1229         return this;
1230     }
1231 
1232     /**
1233      * Sets the display of debug information.
1234      *
1235      * @param debug A boolean.
1236      * @since 1.0
1237      */
1238     public void setDebug(final boolean debug) {
1239         this.debug = debug;
1240     }
1241 
1242     /**
1243      * Sets 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
1244      * charset of {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII
1245      * characters; otherwise, it is used as is.
1246      *
1247      * @param email A String.
1248      * @return An Email.
1249      * @throws EmailException Indicates an invalid email address.
1250      * @since 1.0
1251      */
1252     public Email setFrom(final String email) throws EmailException {
1253         return setFrom(email, null);
1254     }
1255 
1256     /**
1257      * Sets 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
1258      * {@link #setCharset(String)}. If it is not set, it will be encoded using the Java platform's default charset (UTF-16) if it contains non-ASCII characters;
1259      * otherwise, it is used as is.
1260      *
1261      * @param email A String.
1262      * @param name  A String.
1263      * @return An Email.
1264      * @throws EmailException Indicates an invalid email address.
1265      * @since 1.0
1266      */
1267     public Email setFrom(final String email, final String name) throws EmailException {
1268         return setFrom(email, name, charset);
1269     }
1270 
1271     /**
1272      * Sets the FROM field of the email to use the specified address, personal name, and charset encoding for the name.
1273      *
1274      * @param email   A String.
1275      * @param name    A String.
1276      * @param charset The charset to encode the name with.
1277      * @return An Email.
1278      * @throws EmailException Indicates an invalid email address or charset.
1279      * @since 1.1
1280      */
1281     public Email setFrom(final String email, final String name, final String charset) throws EmailException {
1282         fromAddress = createInternetAddress(email, name, charset);
1283         return this;
1284     }
1285 
1286     /**
1287      * Sets the From address.
1288      *
1289      * @param fromAddress the From address.
1290      * @return {@code this} instance.
1291      * @since 1.6.0
1292      */
1293     public Email setFromAddress(final InternetAddress fromAddress) {
1294         this.fromAddress = fromAddress;
1295         return this;
1296 
1297     }
1298 
1299     /**
1300      * Sets the mail headers. Example:
1301      *
1302      * X-Mailer: Sendmail, X-Priority: 1( highest ) or 2( high ) 3( normal ) 4( low ) and 5( lowest ) Disposition-Notification-To: user@domain.net
1303      *
1304      * @param map A Map.
1305      * @throws IllegalArgumentException if either of the provided header / value is null or empty
1306      * @since 1.0
1307      */
1308     public void setHeaders(final Map<String, String> map) {
1309         headers.clear();
1310         for (final Map.Entry<String, String> entry : map.entrySet()) {
1311             addHeader(entry.getKey(), entry.getValue());
1312         }
1313     }
1314 
1315     /**
1316      * Sets the hostname of the outgoing mail server.
1317      *
1318      * @param hostName aHostName
1319      * @throws IllegalStateException if the mail session is already initialized
1320      * @since 1.0
1321      */
1322     public void setHostName(final String hostName) {
1323         checkSessionAlreadyInitialized();
1324         this.hostName = hostName;
1325     }
1326 
1327     /**
1328      * Sets 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
1329      * with a DefaultAuthenticator. This is a convenience but might come unexpected.
1330      *
1331      * If mail authentication is used but NO user name and password is supplied the implementation assumes that you have set a authenticator and will use the
1332      * existing mail session (as expected).
1333      *
1334      * @param session mail session to be used
1335      * @throws NullPointerException if {@code aSession} is {@code null}
1336      * @since 1.0
1337      */
1338     public void setMailSession(final Session session) {
1339         Objects.requireNonNull(session, "no mail session supplied");
1340 
1341         final Properties sessionProperties = session.getProperties();
1342         final String auth = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_AUTH);
1343 
1344         if (Boolean.parseBoolean(auth)) {
1345             final String userName = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_USER);
1346             final String password = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_PASSWORD);
1347 
1348             if (EmailUtils.isNotEmpty(userName) && EmailUtils.isNotEmpty(password)) {
1349                 // only create a new mail session with an authenticator if
1350                 // authentication is required and no user name is given
1351                 authenticator = new DefaultAuthenticator(userName, password);
1352                 this.session = Session.getInstance(sessionProperties, authenticator);
1353             } else {
1354                 // assume that the given mail session contains a working authenticator
1355                 this.session = session;
1356             }
1357         } else {
1358             this.session = session;
1359         }
1360     }
1361 
1362     /**
1363      * Sets a mail Session object from a JNDI directory.
1364      *
1365      * @param jndiName name of JNDI resource (javax.mail.Session type), resource if searched in java:comp/env if name does not start with "java:"
1366      * @throws IllegalArgumentException if the JNDI name is null or empty
1367      * @throws NamingException          if the resource cannot be retrieved from JNDI directory
1368      * @since 1.1
1369      */
1370     public void setMailSessionFromJNDI(final String jndiName) throws NamingException {
1371         if (EmailUtils.isEmpty(jndiName)) {
1372             throw new IllegalArgumentException("JNDI name missing");
1373         }
1374         Context ctx = null;
1375         if (jndiName.startsWith("java:")) {
1376             ctx = new InitialContext();
1377         } else {
1378             ctx = (Context) new InitialContext().lookup("java:comp/env");
1379 
1380         }
1381         setMailSession((Session) ctx.lookup(jndiName));
1382     }
1383 
1384     /**
1385      * Sets the MIME message.
1386      *
1387      * @param message the MIME message.
1388      */
1389     public void setMessage(final MimeMessage message) {
1390         this.message = message;
1391     }
1392 
1393     /**
1394      * Sets the content of the mail. It should be overridden by the subclasses.
1395      *
1396      * @param msg A String.
1397      * @return An Email.
1398      * @throws EmailException generic exception.
1399      * @since 1.0
1400      */
1401     public abstract Email setMsg(String msg) throws EmailException;
1402 
1403     /**
1404      * Sets whether to use POP3 before SMTP, and if so the settings.
1405      *
1406      * @param popBeforeSmtp whether to use POP3 before SMTP, and if so the settings.
1407      * @return {@code this} instance.
1408      * @since 1.6.0
1409      */
1410     public Email setPopBeforeSmtp(final boolean popBeforeSmtp) {
1411         this.popBeforeSmtp = popBeforeSmtp;
1412         return this;
1413 
1414     }
1415 
1416     /**
1417      * Sets details regarding "POP3 before SMTP" authentication.
1418      *
1419      * @param popBeforeSmtp Whether or not to log into POP3 server before sending mail.
1420      * @param popHost       The POP3 host to use.
1421      * @param popUserName   The POP3 user name.
1422      * @param popPassword   The POP3 password.
1423      * @since 1.0
1424      */
1425     public void setPopBeforeSmtp(final boolean popBeforeSmtp, final String popHost, final String popUserName, final String popPassword) {
1426         this.popBeforeSmtp = popBeforeSmtp;
1427         this.popHost = popHost;
1428         this.popUsername = popUserName;
1429         this.popPassword = popPassword;
1430     }
1431 
1432     /**
1433      * Sets the POP3 host.
1434      *
1435      * @param popHost The POP3 host.
1436      * @return {@code this} instance.
1437      * @since 1.6.0
1438      */
1439     public Email setPopHost(final String popHost) {
1440         this.popHost = popHost;
1441         return this;
1442 
1443     }
1444 
1445     /**
1446      * Sets the POP3 password.
1447      *
1448      * @param popPassword the POP3 password.
1449      * @return {@code this} instance.
1450      * @since 1.6.0
1451      */
1452     public Email setPopPassword(final String popPassword) {
1453         this.popPassword = popPassword;
1454         return this;
1455 
1456     }
1457 
1458     /**
1459      * Sets the POP3 user name.
1460      *
1461      * @param popUserName the POP3 user name.
1462      * @return {@code this} instance.
1463      * @since 1.6.0
1464      */
1465     public Email setPopUsername(final String popUserName) {
1466         this.popUsername = popUserName;
1467         return this;
1468 
1469     }
1470 
1471     /**
1472      * Sets a list of reply to addresses. All elements in the specified {@code Collection} are expected to be of type
1473      * {@code java.mail.internet.InternetAddress}.
1474      *
1475      * @param collection collection of {@code InternetAddress} objects
1476      * @return An Email.
1477      * @throws EmailException Indicates an invalid email address
1478      * @see javax.mail.internet.InternetAddress
1479      * @since 1.1
1480      */
1481     public Email setReplyTo(final Collection<InternetAddress> collection) throws EmailException {
1482         EmailException.checkNonEmpty(collection, () -> "Reply to list invalid");
1483         replyList = new ArrayList<>(collection);
1484         return this;
1485     }
1486 
1487     /**
1488      * Sets whether the email is partially send in case of invalid addresses.
1489      * <p>
1490      * 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
1491      * send mode is enabled (emails to valid addresses will be transmitted). In case the email server does not reject invalid addresses immediately, but return
1492      * a bounce message, no exception will be thrown by the {@link #send()} method.
1493      * </p>
1494      *
1495      * @param sendPartial whether to enable partial send mode
1496      * @return An Email.
1497      * @throws IllegalStateException if the mail session is already initialized
1498      * @since 1.3.2
1499      */
1500     public Email setSendPartial(final boolean sendPartial) {
1501         checkSessionAlreadyInitialized();
1502         this.sendPartial = sendPartial;
1503         return this;
1504     }
1505 
1506     /**
1507      * Sets the sent date for the email. The sent date will default to the current date if not explicitly set.
1508      *
1509      * @param date Date to use as the sent date on the email
1510      * @since 1.0
1511      */
1512     public void setSentDate(final Date date) {
1513         if (date != null) {
1514             // create a separate instance to keep findbugs happy
1515             sentDate = new Date(date.getTime());
1516         }
1517     }
1518 
1519     /**
1520      * Sets the non-SSL port number of the outgoing mail server.
1521      *
1522      * @param portNumber aPortNumber
1523      * @throws IllegalArgumentException if the port number is &lt; 1
1524      * @throws IllegalStateException    if the mail session is already initialized
1525      * @since 1.0
1526      * @see #setSslSmtpPort(String)
1527      */
1528     public void setSmtpPort(final int portNumber) {
1529         checkSessionAlreadyInitialized();
1530         if (portNumber < 1) {
1531             throw new IllegalArgumentException("Cannot connect to a port number that is less than 1 ( " + portNumber + " )");
1532         }
1533         this.smtpPort = Integer.toString(portNumber);
1534     }
1535 
1536     /**
1537      * Sets the socket connection timeout value in milliseconds. Default is a 60 second timeout.
1538      *
1539      * @param socketConnectionTimeout the connection timeout
1540      * @throws IllegalStateException if the mail session is already initialized
1541      * @since 1.6.0
1542      */
1543     public void setSocketConnectionTimeout(final Duration socketConnectionTimeout) {
1544         checkSessionAlreadyInitialized();
1545         this.socketConnectionTimeout = Math.toIntExact(socketConnectionTimeout.toMillis());
1546     }
1547 
1548     /**
1549      * Sets the socket I/O timeout value in milliseconds. Default is 60 second timeout.
1550      *
1551      * @param socketTimeout the socket I/O timeout
1552      * @throws IllegalStateException if the mail session is already initialized
1553      * @since 1.6.0
1554      */
1555     public void setSocketTimeout(final Duration socketTimeout) {
1556         checkSessionAlreadyInitialized();
1557         this.socketTimeout = Math.toIntExact(socketTimeout.toMillis());
1558     }
1559 
1560     /**
1561      * Sets whether the server identity is checked as specified by RFC 2595
1562      *
1563      * @param sslCheckServerIdentity whether to enable server identity check
1564      * @return An Email.
1565      * @throws IllegalStateException if the mail session is already initialized
1566      * @since 1.3
1567      */
1568     public Email setSSLCheckServerIdentity(final boolean sslCheckServerIdentity) {
1569         checkSessionAlreadyInitialized();
1570         this.sslCheckServerIdentity = sslCheckServerIdentity;
1571         return this;
1572     }
1573 
1574     /**
1575      * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS). Takes precedence over
1576      * {@link #setStartTLSRequired(boolean)}
1577      * <p>
1578      * Defaults to {@link #sslSmtpPort}; can be overridden by using {@link #setSslSmtpPort(String)}
1579      * </p>
1580      *
1581      * @param ssl whether to enable the SSL transport
1582      * @return An Email.
1583      * @throws IllegalStateException if the mail session is already initialized
1584      * @since 1.3
1585      */
1586     public Email setSSLOnConnect(final boolean ssl) {
1587         checkSessionAlreadyInitialized();
1588         this.sslOnConnect = ssl;
1589         this.ssl = ssl;
1590         return this;
1591     }
1592 
1593     /**
1594      * Sets the SSL port to use for the SMTP transport. Defaults to the standard port, 465.
1595      *
1596      * @param sslSmtpPort the SSL port to use for the SMTP transport
1597      * @throws IllegalStateException if the mail session is already initialized
1598      * @see #setSmtpPort(int)
1599      */
1600     public void setSslSmtpPort(final String sslSmtpPort) {
1601         checkSessionAlreadyInitialized();
1602         this.sslSmtpPort = sslSmtpPort;
1603     }
1604 
1605     /**
1606      * Sets or disable the STARTTLS encryption.
1607      *
1608      * @param startTlsEnabled true if STARTTLS requested, false otherwise
1609      * @return An Email.
1610      * @throws IllegalStateException if the mail session is already initialized
1611      * @since 1.3
1612      */
1613     public Email setStartTLSEnabled(final boolean startTlsEnabled) {
1614         checkSessionAlreadyInitialized();
1615         this.startTlsEnabled = startTlsEnabled;
1616         this.tls = startTlsEnabled;
1617         return this;
1618     }
1619 
1620     /**
1621      * Sets or disable the required STARTTLS encryption.
1622      * <p>
1623      * Defaults to {@link #smtpPort}; can be overridden by using {@link #setSmtpPort(int)}
1624      * </p>
1625      *
1626      * @param startTlsRequired true if STARTTLS requested, false otherwise
1627      * @return An Email.
1628      * @throws IllegalStateException if the mail session is already initialized
1629      * @since 1.3
1630      */
1631     public Email setStartTLSRequired(final boolean startTlsRequired) {
1632         checkSessionAlreadyInitialized();
1633         this.startTlsRequired = startTlsRequired;
1634         return this;
1635     }
1636 
1637     /**
1638      * Sets the email subject. Replaces end-of-line characters with spaces.
1639      *
1640      * @param aSubject A String.
1641      * @return An Email.
1642      * @since 1.0
1643      */
1644     public Email setSubject(final String aSubject) {
1645         this.subject = EmailUtils.replaceEndOfLineCharactersWithSpaces(aSubject);
1646         return this;
1647     }
1648 
1649     /**
1650      * Sets a list of "TO" addresses. All elements in the specified {@code Collection} are expected to be of type {@code java.mail.internet.InternetAddress}.
1651      *
1652      * @param collection collection of {@code InternetAddress} objects.
1653      * @return An Email.
1654      * @throws EmailException Indicates an invalid email address.
1655      * @see javax.mail.internet.InternetAddress
1656      * @since 1.0
1657      */
1658     public Email setTo(final Collection<InternetAddress> collection) throws EmailException {
1659         EmailException.checkNonEmpty(collection, () -> "To list invalid");
1660         this.toList = new ArrayList<>(collection);
1661         return this;
1662     }
1663 
1664     /**
1665      * Converts to copy List of known InternetAddress objects into an array.
1666      *
1667      * @param list A List.
1668      * @return An InternetAddress[].
1669      * @since 1.0
1670      */
1671     protected InternetAddress[] toInternetAddressArray(final List<InternetAddress> list) {
1672         return list.toArray(EMPTY_INTERNET_ADDRESS_ARRAY);
1673     }
1674 
1675     /**
1676      * Updates the contentType.
1677      *
1678      * @param contentType aContentType
1679      * @since 1.2
1680      */
1681     public void updateContentType(final String contentType) {
1682         if (EmailUtils.isEmpty(contentType)) {
1683             this.contentType = null;
1684         } else {
1685             // set the content type
1686             this.contentType = contentType;
1687             // set the charset if the input was properly formed
1688             final String strMarker = "; charset=";
1689             int charsetPos = EmailUtils.toLower(contentType).indexOf(strMarker);
1690             if (charsetPos != -1) {
1691                 // find the next space (after the marker)
1692                 charsetPos += strMarker.length();
1693                 final int intCharsetEnd = EmailUtils.toLower(contentType).indexOf(" ", charsetPos);
1694                 if (intCharsetEnd != -1) {
1695                     this.charset = contentType.substring(charsetPos, intCharsetEnd);
1696                 } else {
1697                     this.charset = contentType.substring(charsetPos);
1698                 }
1699             } else if (this.contentType.startsWith("text/") && EmailUtils.isNotEmpty(this.charset)) {
1700                 // use the default charset, if one exists, for messages
1701                 // whose content-type is some form of text.
1702                 final StringBuilder contentTypeBuf = new StringBuilder(this.contentType);
1703                 contentTypeBuf.append(strMarker);
1704                 contentTypeBuf.append(this.charset);
1705                 this.contentType = contentTypeBuf.toString();
1706             }
1707         }
1708     }
1709 }