Class EMailSender
- All Implemented Interfaces:
de.gustavblass.commons.Copyable<EMailSender>
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final PatternThe regular expression pattern to extract the boundary from the Content-Type header of a MIME message.private final @NonNull io.github.bucket4j.BucketRate-limits all requests to the e-mail server to prevent spamming it and potentially getting banned.private static final @NonNull StringThe default user-agent string sent with the e-mail.private final @NonNull jakarta.mail.internet.InternetAddressThe user's e-mail address at the e-mail server.static final intprivate @NonNull StringThe domain name of the IMAP server that will be used to store the sent e-mails in the sent-folder of the user's inbox.private intThe port of the IMAP server that will be used to store the sent e-mails in the sent-folder of the user's inbox.private byte @Nullable []The passphrase to decrypt thesecretKey.private static final org.apache.logging.log4j.Loggerstatic final intPort numbers are 16-bit unsigned integers, thus ranging from 0 to 65535.private final char @NonNull []static final StringThe branded name of this library (instead of the generic name “EMailSender”).private @NonNull PropertiesThe configuration for sending the e-mails.private byte @Nullable []The secret (a.k.a. private) OpenPGP key to sign the e-mails with (regardless of encryption).private static final String[]Common names for the sent-folder in various IMAP servers.private @NonNull jakarta.mail.SessionThe e-mail session that is used to send the e-mails.static final intprivate @NonNull StringThe domain name of the SMTP server that will be used to send the e-mails.private intThe port of the SMTP server that will be used to send the e-mails.private @Nullable StringThe user-agent string to send with the e-mail.static final @NonNull com.fasterxml.jackson.core.VersionThe current version of this library. -
Constructor Summary
ConstructorsConstructorDescriptionEMailSender(@NonNull String smtpHost, @NonNull String imapHost, @NonNull jakarta.mail.internet.InternetAddress email, char @NonNull [] password) Constructs a new EMailSender with the given SMTP credentials for the given e-mail server.EMailSender(@NonNull String smtpHost, Integer smtpPort, @NonNull String imapHost, int imapPort, @NonNull jakarta.mail.internet.InternetAddress email, char @NonNull [] password) Constructs a new EMailSender with the given SMTP credentials for the given e-mail server. -
Method Summary
Modifier and TypeMethodDescriptionprivate @NonNull jakarta.mail.internet.MimeMessageconstructEmail(@NonNull DraftEmail draft) Constructs a newMimeMessagefrom the specifiedDraftEmailprivate @NonNull jakarta.mail.internet.MimeMultipartconstructMultipart(String message, Iterable<File> attachments) Constructs a MIME multipart from the given e-mail message and files attached.@NonNull EMailSendercopy()voidenableSigning(byte @NonNull [] secretKey) Specifies that the encrypted message should be signed with the given secret key.voidenableSigning(byte @NonNull [] secretKey, byte @NonNull [] passphrase) Specifies that the encrypted message should be signed with the given secret key.private voidencryptMessage(@NonNull DraftEmail eMail, @NonNull jakarta.mail.internet.MimeMessage encryptedEmail) private @NonNull FileencryptMessage(@NonNull jakarta.mail.internet.MimeMultipart message, @NonNull Set<String> pgpPublicKeys, @NonNull String boundary) Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard.booleanCompares this EMailSender with the givenObject.private voidInitialises thepropertieswith the default settings for sending e-mails via the SMTP server.parseBoundary(@NonNull String contentTypeHeader) voidDeletes theuserAgent.private voidsaveEmailToSentFolder(@NonNull jakarta.mail.internet.MimeMessage email) Tries to store the given e-mail in the sent-folder of the user's e-mail account, using the IMAP protocol.@NonNull EMailSender.SentEmailsend(@NonNull DraftEmail eMail) Sends an e-mail from the sender using thepasswordspecified in the constructor to log in at the SMTP server.private @NonNull EMailSender.SentEmailsendEncryptedEmail(@NonNull DraftEmail eMail) voidsetImapHost(@NonNull String host) Updates theimapHostwith the given domain name.voidsetImapPort(int port) Updates theimapPortwith the given port number.voidsetSecretKey(byte @NonNull [] secretKey) Updates thesecretKeyused to sign the message and deletes thekeyPassword.voidsetSecretKey(byte @NonNull [] secretKey, byte @NonNull [] passphrase) Updates thesecretKeyused to sign the message and sets thekeyPassword.voidsetSmtpHost(@NonNull String host) Updates thesmtpHostwith the given domain name.voidsetSmtpPort(int port) Updates thesmtpPortwith the given port number.voidsetUserAgent(@NonNull String userAgent) Updates theuserAgent.@NonNull StringtoString()voidupdateSmtpConnection(@NonNull SmtpConnectionSecurity connection) Updates thepropertiesto use the givenSmtpConnectionSecurityfor sending e-mails.private static @NonNull Optional<ByteArrayOutputStream> writeEmail(@NonNull jakarta.mail.Message email) Converts the given e-mail to its binary representation (in MIME format).
-
Field Details
-
LOG
private static final org.apache.logging.log4j.Logger LOG -
PRODUCT_NAME
The branded name of this library (instead of the generic name “EMailSender”).- See Also:
-
VERSION
@NonNull public static final @NonNull com.fasterxml.jackson.core.Version VERSIONThe current version of this library. -
DEFAULT_USER_AGENT
The default user-agent string sent with the e-mail. -
bucket
@NonNull private final @NonNull io.github.bucket4j.Bucket bucketRate-limits all requests to the e-mail server to prevent spamming it and potentially getting banned. -
MAXIMUM_PORT_NUMBER
public static final int MAXIMUM_PORT_NUMBERPort numbers are 16-bit unsigned integers, thus ranging from 0 to 65535. -
SMTP_PORT
-
IMAP_PORT
-
BOUNDARY_PATTERN
The regular expression pattern to extract the boundary from the Content-Type header of a MIME message.- See Also:
-
SENT_FOLDER_NAMES
Common names for the sent-folder in various IMAP servers. -
smtpHost
-
smtpPort
private int smtpPortThe port of the SMTP server that will be used to send the e-mails. -
imapHost
-
imapPort
private int imapPortThe port of the IMAP server that will be used to store the sent e-mails in the sent-folder of the user's inbox. -
email
@NonNull private final @NonNull jakarta.mail.internet.InternetAddress emailThe user's e-mail address at the e-mail server. Will be used as the sender of the e-mail. -
password
-
secretKey
private byte @Nullable [] secretKeyThe secret (a.k.a. private) OpenPGP key to sign the e-mails with (regardless of encryption). -
keyPassword
private byte @Nullable [] keyPasswordThe passphrase to decrypt thesecretKey. Null if the secret key is not passphrase-protected. -
userAgent
The user-agent string to send with the e-mail. -
properties
The configuration for sending the e-mails. -
session
@NonNull private @NonNull jakarta.mail.Session sessionThe e-mail session that is used to send the e-mails.
-
-
Constructor Details
-
EMailSender
public EMailSender(@NonNull @NonNull String smtpHost, @NonNull @NonNull String imapHost, @NonNull @NonNull jakarta.mail.internet.InternetAddress email, char @NonNull [] password) throws de.gustavblass.commons.exceptions.IllegalArgumentException Constructs a new EMailSender with the given SMTP credentials for the given e-mail server.- Parameters:
smtpHost- The domain name of the SMTP server. The default port will be used.imapHost- The domain name of the IMAP server. The default port will be used.email- The user's e-mail address at the given e-mail server. Will be used as the sender of the e-mail.password- The user's password for the e-mail server.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given SMTP or IMAP host is not a valid domain name.
-
EMailSender
public EMailSender(@NonNull @NonNull String smtpHost, Integer smtpPort, @NonNull @NonNull String imapHost, int imapPort, @NonNull @NonNull jakarta.mail.internet.InternetAddress email, char @NonNull [] password) throws de.gustavblass.commons.exceptions.IllegalArgumentException Constructs a new EMailSender with the given SMTP credentials for the given e-mail server.- Parameters:
smtpHost- The domain name of the SMTP server.smtpPort- The port number of the SMTP server. Must be between 0 and 65535.imapHost- The domain name of the IMAP server.imapPort- The port number of the IMAP server. Must be between 0 and 65535.email- The user's e-mail address at the given e-mail server. Will be used as the sender of the e-mail.password- The user's password for the e-mail server.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given SMTP or IMAP host is not a valid domain name or if the given port numbers are not between 0 and 65535.
-
-
Method Details
-
setSmtpHost
public void setSmtpHost(@NonNull @NonNull String host) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates thesmtpHostwith the given domain name.- Parameters:
host- The new domain name of the SMTP server.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given host is not a valid domain name.
-
setImapHost
public void setImapHost(@NonNull @NonNull String host) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates theimapHostwith the given domain name.- Parameters:
host- The new domain name of the IMAP server.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given host is not a valid domain name.
-
setSmtpPort
public void setSmtpPort(int port) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates thesmtpPortwith the given port number.- Parameters:
port- The new port number of the SMTP server. Must be between 0 and 65535.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given port number is invalid.
-
setImapPort
public void setImapPort(int port) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates theimapPortwith the given port number.- Parameters:
port- The new port number of the IMAP server. Must be between 0 and 65535.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given port number is invalid.
-
setUserAgent
public void setUserAgent(@NonNull @NonNull String userAgent) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates theuserAgent.- Parameters:
userAgent- The new user-agent string to send with the e-mail. Must not be blank.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given user-agent string is blank.
-
resetUserAgent
public void resetUserAgent()Deletes theuserAgent. -
initialiseProperties
private void initialiseProperties()Initialises thepropertieswith the default settings for sending e-mails via the SMTP server. -
updateSmtpConnection
Updates thepropertiesto use the givenSmtpConnectionSecurityfor sending e-mails.- Parameters:
connection- Whether to use SSL or STARTTLS for the connection to the SMTP server.
-
enableSigning
public void enableSigning(byte @NonNull [] secretKey) throws de.gustavblass.commons.exceptions.IllegalArgumentException Specifies that the encrypted message should be signed with the given secret key.- Parameters:
secretKey- The secret (a.k.a. private) key to sign the message with. Must not be empty or require a passphrase.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given secrets key is empty.
-
enableSigning
public void enableSigning(byte @NonNull [] secretKey, byte @NonNull [] passphrase) throws de.gustavblass.commons.exceptions.IllegalArgumentException Specifies that the encrypted message should be signed with the given secret key.- Parameters:
secretKey- The secret (a.k.a. private) key to sign the message with. Must not be empty.passphrase- The passphrase to decrypt the secret key.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given secret key is empty or if the passphrase is empty.
-
setSecretKey
public void setSecretKey(byte @NonNull [] secretKey) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates thesecretKeyused to sign the message and deletes thekeyPassword.- Parameters:
secretKey- The new secret (a.k.a. private) key to sign the message with. Must not be empty or require a passphrase.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given secret key is empty.
-
setSecretKey
public void setSecretKey(byte @NonNull [] secretKey, byte @NonNull [] passphrase) throws de.gustavblass.commons.exceptions.IllegalArgumentException Updates thesecretKeyused to sign the message and sets thekeyPassword.- Parameters:
secretKey- The new secret (a.k.a. private) key to sign the message with. Must not be empty.passphrase- The passphrase to decrypt the secret key.- Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException- If the given secret key is empty or if the passphrase is empty.
-
send
@NonNull public @NonNull EMailSender.SentEmail send(@NonNull @NonNull DraftEmail eMail) throws InterruptedException, de.gustavblass.commons.exceptions.IllegalArgumentException, IOException Sends an e-mail from the sender using thepasswordspecified in the constructor to log in at the SMTP server.- Parameters:
eMail- The e-mail to send.- Returns:
The entire e-mail as it was sent, ready to be written to an
.emlfile.- unencryptedEmail: A copy of the e-mail before encryption or (if sent
unencrypted) the exact e-mail as it was sent. Empty if
fallBackToUnencryptedis false. - encryptedEmail: If the e-mail was sent encrypted, the e-mail as it was sent. Otherwise, empty.
One or both may be empty if the e-mail could not be written to an
OutputStream. If empty, this does not imply that the e-mail was not sent; if the e-mail could not be sent, an exception will be thrown instead.- unencryptedEmail: A copy of the e-mail before encryption or (if sent
unencrypted) the exact e-mail as it was sent. Empty if
- Throws:
IOException- If the e-mail could not be sent.de.gustavblass.commons.exceptions.IllegalArgumentException- If the e-mail could not be constructed from the given parameters (should not happen).InterruptedException- If the thread was interrupted while sending the e-mail.
-
sendEncryptedEmail
@NonNull private @NonNull EMailSender.SentEmail sendEncryptedEmail(@NonNull @NonNull DraftEmail eMail) throws InterruptedException, IOException, jakarta.mail.MessagingException - Throws:
InterruptedExceptionIOExceptionjakarta.mail.MessagingException
-
encryptMessage
private void encryptMessage(@NonNull @NonNull DraftEmail eMail, @NonNull @NonNull jakarta.mail.internet.MimeMessage encryptedEmail) throws jakarta.mail.MessagingException, IOException, de.gustavblass.commons.exceptions.IllegalArgumentException - Throws:
jakarta.mail.MessagingExceptionIOExceptionde.gustavblass.commons.exceptions.IllegalArgumentException
-
saveEmailToSentFolder
private void saveEmailToSentFolder(@NonNull @NonNull jakarta.mail.internet.MimeMessage email) throws IOException, de.gustavblass.commons.exceptions.NotFoundException Tries to store the given e-mail in the sent-folder of the user's e-mail account, using the IMAP protocol.- Parameters:
email- The message to store in the sent-folder.- Throws:
IOException- If the connection to the IMAP server failed.de.gustavblass.commons.exceptions.NotFoundException- If the sent-folder could not be found.- Implementation Note:
- Since the sent-folder is not standardised, several names are tried to
find the correct folder. This method throws
NotFoundExceptioninstead ofFolderNotFoundExceptionbecause the constructors of the latter require aFolder.
-
constructEmail
@NonNull private @NonNull jakarta.mail.internet.MimeMessage constructEmail(@NonNull @NonNull DraftEmail draft) throws jakarta.mail.MessagingException, IOException, de.gustavblass.commons.exceptions.IllegalStateException Constructs a newMimeMessagefrom the specifiedDraftEmail- Parameters:
draft- The draft from which to construct the e-mail.- Returns:
- The constructed e-mail.
- Throws:
de.gustavblass.commons.exceptions.IllegalStateException- If the e-mail is from a read-only folder.jakarta.mail.MessagingException- If the e-mail is from a read-only folder.IOException- If the files attached could not be read.
-
constructMultipart
@NonNull private @NonNull jakarta.mail.internet.MimeMultipart constructMultipart(String message, Iterable<File> attachments) throws jakarta.mail.MessagingException, IOException Constructs a MIME multipart from the given e-mail message and files attached.- Parameters:
message- The actual content of the e-mail.attachments- Any number of files to send along with the message.- Returns:
- The e-mail message as a MIME multipart.
- Throws:
jakarta.mail.MessagingException- No context specified by the underlying JavaMail API. Sorry.IOException- If the files attached could not be read.
-
writeEmail
@NonNull private static @NonNull Optional<ByteArrayOutputStream> writeEmail(@NonNull @NonNull jakarta.mail.Message email) Converts the given e-mail to its binary representation (in MIME format).- Parameters:
email- The e-mail to convert.- Returns:
- The e-mail as a binary representation in MIME format.
-
encryptMessage
@NonNull private @NonNull File encryptMessage(@NonNull @NonNull jakarta.mail.internet.MimeMultipart message, @NonNull @NonNull Set<String> pgpPublicKeys, @NonNull @NonNull String boundary) throws de.gustavblass.commons.exceptions.IllegalArgumentException Encrypts the given MIME message for the given PGP public keys, following the PGP/MIME standard.- Parameters:
message- The already MIME-formatted message to encrypt. May contain text and several attachments of any file type.pgpPublicKeys- Any number of PGP public keys to encrypt the message with. It does not matter if the keys are of RSA or ECC type, as long as they support encryption.boundary- The boundary used in the MIME message provided. If there is no boundary present, this parameter should be an empty String (if so, no boundary will be specified). Will be added to theContent-Typeheader of the MIME message.- Returns:
- The encrypted message as a
Filewith the nameencrypted.ascin the system's temporary directory. - Throws:
de.gustavblass.commons.exceptions.IllegalArgumentException-If:
- no PGP public keys were provided
- If the message could not be written out
- The message could not be written to the
encrypted.ascPGP/MIME file.
- Implementation Note:
The boundary is needed due to limitations of JavaMail: It does not natively support PGP/MIME encryption and – by default – places a boundary at the top of the MIME message, which does not match the PGP/MIME format. Therefore, we can't encrypt entire MimeMessages but only their
MimeMultipartparts and have to construct the beginning of the MimeMultipart ourselves.This is done by specifying the Content-Type header at the very top, as required for PGP/MIME, including the specified boundary.
Incorrect (JavaMail default)
--=-SiYyK6Oi9/3KVlFTZwJf Content-Type: multipart/mixed; boundary="=-SiYyK6Oi9/3KVlFTZwJf" Content-Type: text/plain Content-Transfer-Encoding: 7bit To whom it may concern, I am writing that you should not read this e-mail. --=-SiYyK6Oi9/3KVlFTZwJf Content-Disposition: attachment; filename="attachment.txt" Content-Type: text/plain; name="attachment.txt"; charset="UTF-8" Content-Transfer-Encoding: base64 4oCcVWx0aW1hdGVseSwgYXJndWluZyB0aGF0IHlvdSBkb24ndCBjYXJlIGFib3V0IHRoZSByaWdo dCB0byBwcml2YWN5IGJlY2F1c2UgeW91IGhhdmUgbm90aGluZyB0byBoaWRlIGlzIG5vCmRpZmZl cmVudCB0aGFuIHNheWluZyB5b3UgZG9uJ3QgY2FyZSBhYm91dCBmcmVlIHNwZWVjaCBiZWNhdXNl IHlvdSBoYXZlIG5vdGhpbmcgdG8gc2F5LuKAnQoK4oCTIEVkd2FyZCBTbm93ZGVu --=-SiYyK6Oi9/3KVlFTZwJf--Correct (manually constructed)
Content-Type: multipart/mixed; boundary="=-SiYyK6Oi9/3KVlFTZwJf" --=-SiYyK6Oi9/3KVlFTZwJf Content-Type: text/plain Content-Transfer-Encoding: 7bit To whom it may concern, I am writing that you should not read this e-mail. --=-SiYyK6Oi9/3KVlFTZwJf Content-Disposition: attachment; filename="attachment.txt" Content-Type: text/plain; name="attachment.txt"; charset="UTF-8" Content-Transfer-Encoding: base64 4oCcVWx0aW1hdGVseSwgYXJndWluZyB0aGF0IHlvdSBkb24ndCBjYXJlIGFib3V0IHRoZSByaWdo dCB0byBwcml2YWN5IGJlY2F1c2UgeW91IGhhdmUgbm90aGluZyB0byBoaWRlIGlzIG5vCmRpZmZl cmVudCB0aGFuIHNheWluZyB5b3UgZG9uJ3QgY2FyZSBhYm91dCBmcmVlIHNwZWVjaCBiZWNhdXNl IHlvdSBoYXZlIG5vdGhpbmcgdG8gc2F5LuKAnQoK4oCTIEVkd2FyZCBTbm93ZGVu --=-SiYyK6Oi9/3KVlFTZwJf--
-
toString
-
copy
- Specified by:
copyin interfacede.gustavblass.commons.Copyable<EMailSender>- Throws:
IllegalStateException
-
equals
Compares this EMailSender with the givenObject.- Overrides:
equalsin classObject- Parameters:
object- The Object to compare this EMailSender with.- Returns:
- True if the given object is an EMailSender whose fields' values match their equivalents in this EMailSender, especially (but not exclusively) if the given EMailSender is the exact same reference as this EMailSender.
- False if the given object is not a EMailSender or if any of the fields' values differ from their equivalents in this EMailSender.
-
parseBoundary
@NonNull private static @NonNull Optional<String> parseBoundary(@NonNull @NonNull String contentTypeHeader) - Parameters:
contentTypeHeader- The value of a MIME message's Content-Type header.- Returns:
- The boundary specified in the given
Content-Typeheader, if present. Empty if no boundary was found. - Implementation Note:
- This method is necessary because JavaMail does not support PGP/MIME encryption natively and does not provide a way to manually specify a boundary for the MIME message. Therefore, the boundary must be extracted from the Content-Type header of the JavaMail-generated MIME message.
-