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