Simple Java Mail Features
(also check configuration and security if you're missing a feature)
Creating and sending emails is very simple with Simple Java Mail; you don't need to know about the mailing RFC, MimeMessage or any other low
level Jakarta Mail API. No anonymous inner classes needed and no large frameworks needed nor XML.
Entry classes
The primary entry classes are EmailBuilder and MailerBuilder. Other entry classes are EmailConverter and JMail (the latter as alternative to using the validation methods on the Mailer instance). Finally, MailerHelper exposes some utilities in case you don't actually need to connect to a server.
Default features
Simple Java Mail will do some basic validation checks so that your email is always populated with enough data. It also checks for CRLF injection attacks. It even verifies email addresses against RFC-2822 and others using JMail. Simple Java Mail also takes care of all the connection and security properties for you.
Builders all the way down
With all the possible ways to configure Email and Mailer instances, the library had only one option left to streamline API, avoid the Telescoping Constructor anti-pattern and keep things manageable: fluent builders.
- With fluent builders, we can tightly control valid combinations, logical decision paths and easily provide alternative methods.
- At the same time we can concentrate all mutation logic in the builders and produce mostly immutable objects.
- Finally, we're able to centralize our documentation around the builders and refer to it from wherever this library is used.
How does Simple Java Mail compare to other mailing libraries?
Checkout the feature comparison matrix to see how Simple Java Mail fares against other libraries, like Apache Commons Email and Spring Mail.
Migrating from an older version?
Checkout the migration notes to see exactly what changed.
- Basic usage
- About the fluent API with the Builder pattern
- Configure once, reuse many times
- Alternative API for almost everything
- Dedicated recipient builders
- Authentication and OAUTH2 support
- Asynchronous sending, simple batches and clustering
- Handling asynchronous mailing result
- Changing the content encoding
- Sending with your own Session instance
- Setting a custom message ID on sent email
- Setting a custom sent date on sent email
- Getting the generated email id after sending
- Sending with SSL and TLS
- SSL and TLS with Google mail
- Adding attachments
- Embedding images
- Setting custom headers
- Setting custom properties on the internal Session
- Routing Jakarta Mail debug output
- Binding the local/source SMTP address
- Sending a Calendar event (iCalendar vEvent)
- Direct access to the internal Session
- Configure delivery / read receipt
- Configure Delivery Status Notification
- Validating Email Addresses
- Converting between email formats
- Setting custom recipient for bouncing emails
- Replying to and forwarding emails
- Send using a proxy
- Testing a server connection
- Serializing Email objects
- Plug your own sending logic
- Limit the maximum email size
Roadmap
Currently everything we can think of is included or on the issue tracker!
Got a suggestion? Please post it in the issue tracker.
Basic usage
Simply build an Email, populate it with your data, build a Mailer and send the Email
instance. The mailer can be created with your own Session instance as well.
A Mailer instance is reusable.
Multiple Mailer instances can form a powerful cluster.
Email email = EmailBuilder.startingBlank()
.from("Michel Baker", "m.baker@mbakery.com")
.withRecipients(new RecipientBuilder()
.withName("mom")
.withAddress("jean.baker@hotmail.com")
.withType(Message.RecipientType.TO)
.build())
.withRecipients(new RecipientBuilder()
.withName("dad")
.withAddress("StevenOakly1963@hotmail.com")
.withType(Message.RecipientType.TO)
.build())
.withSubject("My Bakery is finally open!")
.withPlainText("Mom, Dad. We did the opening ceremony of our bakery!!!")
.withHTMLText("<p>Mom, Dad. We did the opening ceremony of <strong>our bakery</strong>!!!</p>")
.buildEmail();
MailerBuilder
.withSMTPServer("server", 25, "username", "password")
.buildMailer()
.sendMail(email);
About the fluent API with the Builder pattern
The entry classes for the builders are EmailBuilder and MailerBuilder.
For EmailBuilder, the first method initializes the builder in different ways, leaving it with defaults
(EmailBuilder.startingBlank()), preconfiguring it
(EmailBuilder.copying(), EmailBuilder.replyingTo()),
or by setting values otherwise not possible (EmailBuilder.forwarding()).
For MailerBuilder, the first method determines if you get a full builder API or a reduced API because you provided
your own custom Session instance.
If you provide your own session, a lot of properties are presumed to be preconfigured, such as SMTP server details.
Configure once, reuse many times
You can preconfigure a Mailer and use it many times. It is thread-safe.
Mailer inhouseMailer = MailerBuilder
.withSMTPServer("server", 25, "username", "password")
.buildMailer();
inhouseMailer.sendMail(email);
inhouseMailer.sendMail(anotherEmail);
Or as preconfigured Spring bean:
@Bean
public Mailer inhouseMailer() {
return MailerBuilder
.withSMTPServer(...)
.buildMailer();
}
Or the default one from the Spring support module:
@Import(SimpleJavaMailSpringSupport.class)
@Autowired Mailer mailer; // configured completely using default properties
Alternative API for almost everything
Simple Java Mail has alternative ways to do things for almost everything...
For example, when building an email, add
Recipient
objects directly, or use RecipientsBuilder when a list needs RFC822 parsing, default names, fixed names, mixed recipient types
or a group-level S/MIME certificate.
The email builder itself keeps only the canonical withRecipients(...) entry points.
Think of RecipientsBuilder as recipient-group governance. Default values fill gaps, fixed values intentionally override
source data, and S/MIME certificate policies can preserve source recipients, fill missing certificates, replace all certificates or clear them so the
email/mailer S/MIME fallback can apply.
// Add your own Recipient instances
currentEmailBuilder.withRecipients(yourRecipient1, yourRecipient2...);
// Or build comma / semicolon separated recipient lists first
String list = "twister@sweets.com,blue.tongue@sweets.com;honey@sweets.com";
Collection<Recipient> archive = new RecipientsBuilder()
.withDefaultSmimeCertificate(archiveCertificate)
.withRecipientsWithDefaultName("maintenance group", Message.RecipientType.BCC, list)
.buildRecipients();
currentEmailBuilder.withRecipients(archive);
// what about a group with one deviating name?
String list = "bob@sweets.com, gene@sweets.com; Security Group <security@sweets.com>";
Collection<Recipient> stakeholders = new RecipientsBuilder()
.withRecipientsWithDefaultName("stakeholders", Message.RecipientType.TO, list)
.buildRecipients();
currentEmailBuilder.withRecipients(stakeholders);
// bob and gene are named "stakeholders", "Security Group" keeps its own name
// or force both display name and certificate for a controlled group
Collection<Recipient> legal = new RecipientsBuilder()
.withFixedSmimeCertificate(legalDepartmentCertificate)
.withRecipientsWithFixedName("Legal Department", Message.RecipientType.CC,
"reviewer@example.com", "Counsel <counsel@example.com>")
.buildRecipients();
currentEmailBuilder.withRecipients(legal);
// all recipients are shown as "Legal Department" and use the same group certificate
// or strip certificate state from reused recipients
Collection<Recipient> reusedWithoutCertificates = new RecipientsBuilder()
.clearingSmimeCertificates()
.withRecipients(reusedRecipients)
.buildRecipients();
currentEmailBuilder
.withRecipients(reusedWithoutCertificates)
.encryptWithSmime(emailLevelFallbackCertificate);
Through properties:
simplejavamail.defaults.bcc.name=
simplejavamail.defaults.bcc.address=twister@sweets.com,blue.tongue@sweets.com;honey@sweets.com
The email builder recipient surface is intentionally compact:
// EmailPopulatingBuilder
.withRecipients(Recipient... recipients)
.withRecipients(Collection<Recipient> recipients)
.withRecipients(String name, boolean fixedName, RecipientType recipientType, String... oneOrMoreAddressesEach)
// RecipientsBuilder
.withRecipientsWithDefaultName(String name, RecipientType recipientType, String... oneOrMoreAddressesEach)
.withRecipientsWithFixedName(String name, RecipientType recipientType, String... oneOrMoreAddressesEach)
.withRecipientsFromAddressesWithDefaultName(String name, Collection<InternetAddress> addresses, RecipientType recipientType)
.withRecipientsFromAddressesWithFixedName(String name, Collection<InternetAddress> addresses, RecipientType recipientType)
.withDefaultSmimeCertificate(X509Certificate smimeCertificate)
.withFixedSmimeCertificate(X509Certificate smimeCertificate)
.clearingSmimeCertificates()
.withRecipients(Collection<Recipient> recipients)
.withRecipients(Recipient... recipients)
.buildRecipients()
Dedicated recipient builders
If a recipient needs to be assembled outside an email builder call, use the dedicated recipient builders. This keeps address parsing,
recipient type and optional recipient metadata in one small object before it is added to an email. When several recipients belong to one
operational group, RecipientsBuilder can govern that group before it enters the email.
Default names and default certificates only fill missing values. Fixed names and fixed certificates override source data for the whole group.
clearingSmimeCertificates() removes certificate state from reused recipients when the current email should use its own
S/MIME fallback instead.
Recipient-level S/MIME certificates are described in the S/MIME security section.
Recipient alice = new RecipientBuilder()
.withAddressAndNameOrDefault("Alice <alice@example.com>", null)
.withType(Message.RecipientType.TO)
.withSmimeCertificate(aliceCertificate)
.build();
Collection<Recipient> archive = new RecipientsBuilder()
.withDefaultSmimeCertificate(archiveCertificate)
.withRecipientsWithDefaultName("Archive", Message.RecipientType.BCC, "audit@example.com", "records@example.com")
.buildRecipients();
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(alice)
.withRecipients(archive)
.withPlainText("Hello")
.buildEmail();
Authentication and OAUTH2 support
You use one of the builder methods on MailerBuilder for defining the server properties like host, port, username and password.
Simple Java Mail supports plain SMTP (default, but not recommended), SMTPS (legacy SSL) or TLS (recommended). The last authentication option to join the family is OAuth2 (by means of XOAUTH2 which comes built-in in Jakarta Mail, the underlying SMTP framework).
Then depending on the transport strategy that you choose, the password represents the SMTP server password or the OAuth2 token. However, obtaining the OAuth2 token and refreshing tokens etc. is outside the scope of this library, which varies from platform to platform.
MailerBuilder
.withSMTPServer("server host", 587, "username", yourPassword)
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.buildMailer()
.sendMail(email);
MailerBuilder
.withSMTPServer("server host", 587, "username", yourOAuth2Token)
.withTransportStrategy(TransportStrategy.SMTP_OAUTH2)
.buildMailer()
.sendMail(email);
See the security page for a more in-depth explanation of transport strategies.
Asynchronous sending, simple batches and clustering
The default mode is to send emails synchronously, blocking execution until the email was processed completely and the SMTP server sent a successful result.
You can also send asynchronously in parallel or batches, or simply send in a fire-and-forget way. If an authenticated proxy is used, the proxy bridging server is kept alive until the last email has been sent.
Depending on the SMTP server (and proxy server if used) this can greatly influence how fast emails are sent.
mailer.sendMail(email, /* async = */ true);
Or configure it when building the mailer:
Mailer mailer = mailerBuilder
.(..)
.async()
.buildMailer();
mailer.sendMail(email);
Refer to the configuration section on how to set batch-module thread pool defaults or configure the executor service (thread pool manager).
Simple sequential batch sending
If reconnecting for each email is the only overhead you need to avoid, use sendMailsInSimpleBatch(...).
It sends multiple emails sequentially over one SMTP connection and does not require the batch-module.
mailer.sendMailsInSimpleBatch(emails); // uses the mailer's async default
mailer.sendMailsInSimpleBatch(emails, false); // blocks until the batch is done
mailer.sendMailsInSimpleBatch(emails, true); // schedules the whole batch asynchronously
If the caller owns the queue and needs custom work between sends, use
withOpenConnection(...). The scoped sender keeps one SMTP connection open for the callback, but it is still
caller-managed, sequential and not pooled.
mailer.withOpenConnection(sender -> {
while (database.hasPendingMail()) {
Email next = database.nextPendingMail();
sender.sendMail(next);
database.markSent(next);
}
});
Advanced pooled batch processing
For sustained high-volume sending, include the batch-module.
It pools SMTP connections over multiple sends and can cluster multiple pools for different SMTP servers.
Refer to the configuration section for details.
Handling asynchronous mailing result
To handle the successful or erroneous result of an async sent email, you can just register your handlers on the returned CompletableFuture.
Also read this excellent primer on how to process exceptions with CompletableFuture.
Note that non-async sending or testing connections return a CompletableFuture that is completed immediately.
CompletableFuture<Void> f = mailer.sendMail(email, true);
// also mailer.testConnection(email, true)
// or mailerBuilder.async() and then just mailer.sendMail(email)
// one of the many ways you can handle the result:
mailer.testConnection(true)
.whenComplete((result, ex) -> {
if (ex != null) {
System.err.printf("Execution failed %s", ex);
} else {
System.err.printf("Execution completed: %s", result);
}
});
Sending with your own Session instance
If you prefer to use your own preconfigured Session instance and still benefit from Simple Java Mail, you can!
Email email = ...
...
MailerBuilder
.usingSession(yourSession)
.buildMailer()
.sendMail(email);
Changing the content encoding
By default content is encoded in the target MimeMessage or EML using quoted-printable, so the EML is nicely and safely readable. However, you can change it to many other encoders such as base64, which for example is useful if you want to add an extra layer of obfuscation.
The email header that governs this feature is Content-Transfer-Encoding and Simple Java Mail takes care of inserting this header in the right places, which varies depending on the necessary email structure.
------=_Part_2_1226020905.1657715639009
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
We should meet up!
base64 on the other hand, produces the following:
currentEmailBuilder.withContentTransferEncoding(ContentTransferEncoding.BASE_64)
------=_Part_2_1226020905.1657715871203
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
V2Ugc2hvdWxkIG1lZXQgdXAh
Encoding attachments
You can control encoding separately for attachments, so your text files for examples can be encoded differently too.
.withAttachment("invitation.pdf", yourDataSource,
"Invitation flyer", ContentTransferEncoding.BASE_64)
// or fix encoding by creating attachment objects directly
new AttachmentResource(..., ContentTransferEncoding.BINARY)
Available encoders
currentEmailBuilder
.withContentTransferEncoding(ContentTransferEncoding.QUOTED_PRINTABLE)
.withContentTransferEncoding(ContentTransferEncoding.BASE_64)
.withContentTransferEncoding(ContentTransferEncoding.BINARY)
.withContentTransferEncoding(ContentTransferEncoding.B)
.withContentTransferEncoding(ContentTransferEncoding.Q)
.withContentTransferEncoding(ContentTransferEncoding.BIT7)
.withContentTransferEncoding(ContentTransferEncoding.BIT8)
.withContentTransferEncoding(ContentTransferEncoding.UU)
.withContentTransferEncoding(ContentTransferEncoding.X_UU)
.withContentTransferEncoding(ContentTransferEncoding.X_UUE)
// or default back to quoted-printable
.clearContentTransferEncoding()
Encoding body parts separately
The global content-transfer encoding is still the fallback, but plain text, HTML and calendar body parts can override it individually. The same defaults can be supplied through properties.
currentEmailBuilder
.withContentTransferEncoding(ContentTransferEncoding.QUOTED_PRINTABLE)
.withPlainTextContentTransferEncoding(ContentTransferEncoding.BIT7)
.withHTMLTextContentTransferEncoding(ContentTransferEncoding.BASE_64)
.withCalendarTextContentTransferEncoding(ContentTransferEncoding.QUOTED_PRINTABLE)
simplejavamail.defaults.content.transfer.encoding=QUOTED_PRINTABLE
simplejavamail.defaults.body.text.content.transfer.encoding=BIT7
simplejavamail.defaults.body.html.content.transfer.encoding=BASE_64
simplejavamail.defaults.body.calendar.content.transfer.encoding=QUOTED_PRINTABLE
Setting a custom message ID on sent email
Message id's are normally generated by the underlying Jakarta Mail framework, but you can provide your own if required.
Just make sure your own id's conform to the rfc5322 msg-id format standard
currentEmailBuilder.fixingMessageId("<123@456>");
Setting a custom sent date on sent email
Message sent date is normally filled with the current date, but you can provide your own date if required.
currentEmailBuilder.fixingSentDate(new GregorianCalendar(2011, APRIL, 1, 3, 51).getTime());
Getting the generated email id after sending
Sometimes you need the actual ID used in the MimeMessage that went out to the SMTP server. Luckily, it's very easy to retrieve it.
mailer.sendMail(email); // id updated during sending!
email.getId(); // <1420232606.6.1509560747190@Cypher>
Sending with SSL and TLS
Activating SSL or TLS is super easy. Just use the appropriate TransportStrategy enum.
Email email = ...;
MailerBuilder.withTransportStrategy(TransportStrategy.SMTP); // default if omitted
MailerBuilder.withTransportStrategy(TransportStrategy.SMTPS);
MailerBuilder.withTransportStrategy(TransportStrategy.SMTP_TLS);
MailerBuilder.withTransportStrategy(TransportStrategy.SMTP_OAUTH2);
Or with property default:
simplejavamail.transportstrategy=SMTP
# or: SMTPS, SMTP_TLS, SMTP_OAUTH2
Customizing SSL connections further
For maximum control, you can provide your own SSLSocketFactory too:
mailerBuilder
.withCustomSSLFactoryClass(theClassName)
.withCustomSSLFactoryInstance(theInstance) // takes precedence
.buildMailer();
Or with property default:
simplejavamail.custom.sslfactory.class=you.project.YourSSLSocketFactory
SSL and TLS with Google mail
Here's an example of SSL and TLS using gMail.
If you have two-factor login turned on, you need to generate an application specific password from your Google account.
MailerBuilder
.withSMTPServer("smtp.gmail.com", 25, "your user", "your password")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
// or
.withSMTPServer("smtp.gmail.com", 587, "your user", "your password")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
// or
.withSMTPServer("smtp.gmail.com", 465, "your user", "your password")
.withTransportStrategy(TransportStrategy.SMTPS);
Adding attachments
You can add attachments very easily, but you'll have to provide the data yourself. Simple Java Mail accepts byte[]
and DataSource objects.
currentEmailBuilder
.withAttachment("dresscode.txt", new ByteArrayDataSource("Black Tie Optional", "text/plain"))
.withAttachment("location.txt", "On the moon!".getBytes(Charset.defaultCharset()), "text/plain")
// ofcourse it can be anything: a pdf, doc, image, csv or anything else
.withAttachment("invitation.pdf", new FileDataSource("invitation_v8.3.pdf"))
// you can provide your own list of attachments as well
.withAttachments(yourAttachmentResourceCollection))
If for some reason you need the Content-Description header set as well, you can provide a content description on any attachment.
currentEmailBuilder.withAttachment(
"dresscode.txt",
new ByteArrayDataSource("Black Tie Optional", "text/plain"),
"The dresscode for the party")
Which results in something like this:
Content-Type: text/plain; name="dresscode.txt"
Content-Disposition: attachment; filename="dresscode.txt"
Content-ID: <dresscode.txt>
Content-Description: The dresscode for the party
Black Tie Optional
You can further control how the attachment is encoded in the MimeMessage / EML until the data read back from the MimeMessage, by providing a content transfer encoding (resulting in a Content-Transfer-Encoding header for the attachment).
If attachment data is already encoded, use a pre-encoded attachment method. This tells Simple Java Mail to preserve the payload and use
the provided ContentTransferEncoding value instead of encoding the data again.
Attachment resource names and MIME Content-ID values can also be set separately.
currentEmailBuilder
.withPreEncodedAttachment(
"report.pdf",
alreadyBase64EncodedData,
"application/pdf",
ContentTransferEncoding.BASE_64)
.withAttachment(
"report-display-name.pdf",
reportDataSource,
"Monthly report",
ContentTransferEncoding.BASE_64,
"stable-report-content-id")
Embedding images
Embedding images dead simple with two options:
- manual embedding: add
cid:placeholders in the HTML yourself - auto resolution: enable auto-resolving image sources to files, class path resources or URL's
Manual embedding:
currentEmailBuilder.withEmbeddedImage("smiley", new FileDataSource("smiley.jpg"));
currentEmailBuilder.withEmbeddedImage("thumbsup", parseBase64Binary(base64String), "image/png");
currentEmailBuilder.withEmbeddedImage("brand-logo.png", logoDataSource, "brand-logo-cid");
// above example is included in the demo package in MailTestApp.java
// the corresponding HTML should contain the placeholders
<p>Let's go!</p><img src='cid:thumbsup'><br/>
<p>Smile!</p><img src='cid:smiley'>
<p>Logo</p><img src='cid:brand-logo-cid'>
Pre-encoded embedded images work the same way as pre-encoded attachments: provide the already-encoded data and the encoding that is already present.
currentEmailBuilder
.withPreEncodedEmbeddedImage(
"logo.png",
alreadyBase64EncodedLogo,
"image/png",
ContentTransferEncoding.BASE_64)
.withPreEncodedEmbeddedImage(
"logo.png",
logoDataSource,
ContentTransferEncoding.BASE_64,
"stable-logo-content-id")
Auto resolution can be enabled for files, classpath resources and URL's and you can configure base dirs and optionally enforce that referenced resources should all be resolved successfully. Useful for when you allow end users to freely enter HTML.
Auto resolution:
<p>Let's go!</p><img src='smiley.jpg'><br/>
<p>Smile!</p><img src='https://www.myplace.com/smiley.png'>
// results in the following HTML when building the email:
<p>Let's go!</p><img src='cid:etweffxdeu'><br/>
<p>Smile!</p><img src='cid:sienfddiew'>
email.getEmbeddedImages(); // now contains two data sources!
To enable this:
emailBuilder
// enable auto resolution
.withEmbeddedImageAutoResolutionForFiles(true) // default false
.withEmbeddedImageAutoResolutionForClassPathResources(true) // default false
.withEmbeddedImageAutoResolutionForURLs(true) // default false
// support for base dirs
.withEmbeddedImageBaseDir(RESOURCES_PATH + "/images")
.withEmbeddedImageBaseUrl("https://www.simplejavamail.org/static/")
.withEmbeddedImageBaseClassPath("/images")
// allow resources outside of basedir (careful, potential security attack surface!)
.allowingEmbeddedImageOutsideBaseDir(true) // default false
.allowingEmbeddedImageOutsideBaseClassPath(true) // default false
.allowingEmbeddedImageOutsideBaseUrl(true) // default false
// fail if a resource couldn't be resolved
.embeddedImageAutoResolutionMustBeSuccesful(true) // default false (lenient mode)
Also works with properties:
simplejavamail.embeddedimages.dynamicresolution.enable.dir=true
simplejavamail.embeddedimages.dynamicresolution.enable.url=true
simplejavamail.embeddedimages.dynamicresolution.enable.classpath=true
simplejavamail.embeddedimages.dynamicresolution.base.dir=...
simplejavamail.embeddedimages.dynamicresolution.base.url=https://www.simplejavamail.org/static/
simplejavamail.embeddedimages.dynamicresolution.base.classpath=/images
simplejavamail.embeddedimages.dynamicresolution.outside.base.dir=true
simplejavamail.embeddedimages.dynamicresolution.outside.base.classpath=true
simplejavamail.embeddedimages.dynamicresolution.outside.base.url=true
simplejavamail.embeddedimages.dynamicresolution.mustbesuccesful=true
Setting custom headers
Sometimes you need extra headers in your email because your email server, recipient server or your email client needs it. Or perhaps you have a proxy or monitoring setup in between mail servers. Whatever the case, adding headers is easy.
currentEmailBuilder
.withHeader("X-Priority", 2);
.withHeader("X-MC-GoogleAnalyticsCampaign", "halloween_sale");
.withHeader("X-MEETUP-RECIP-ID", "71415272");
.withHeader("X-my-custom-header", "foo");
// or
.withHeaders(yourHeadersMap);
Setting custom properties on the internal Session
In case you need to modify the internal Session object itself, because you need a tailored configuration that is supported by the underlying
Jakarta Mail, that too is very easy.
currentMailerBuilder
.withProperty("mail.smtp.timeout", 30 * 1000)
.withProperty("mail.smtp.connectiontimeout", 10 * 1000)
// or
.withProperties(yourPropertiesObject)
.withProperties(yourPropertiesMap)
You can also set some default properties to automatically be added.
Every property prepended with simplejavamail.extraproperties will be loaded directly on the internal Session object.
simplejavamail.extraproperties.my.extra.property=value
simplejavamail.extraproperties.mail.smtp.ssl.socketFactory.class=org.mypackage.MySSLSocketFactory
simplejavamail.extraproperties.mail.smtp.timeout=30000
Routing Jakarta Mail debug output
Jakarta Mail can produce low-level protocol debug output. Simple Java Mail can keep the old console behavior, route it to stderr, or send it to
the org.simplejavamail.javamail.debug SLF4J logger.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, "user", "password")
.withDebugLogging(true)
.withDebugOutput(SessionDebugOutput.SLF4J)
.buildMailer();
simplejavamail.javaxmail.debug=true
simplejavamail.javaxmail.debug.out=SLF4J
Binding the local/source SMTP address
On machines with multiple local IP addresses, the outgoing SMTP socket can be bound to a specific local/source address. Simple Java Mail applies this to the right Jakarta Mail property for the configured transport strategy.
Leave the local port empty unless you specifically need it. Binding a fixed local port can conflict with another connection.
Mailer mailer = MailerBuilder
.withSMTPServer("smtp.example.com", 587, "user", "password")
.withTransportStrategy(TransportStrategy.SMTP_TLS)
.withLocalBindAddress("203.0.113.10")
.buildMailer();
simplejavamail.smtp.localaddress=203.0.113.10
simplejavamail.smtp.localport=25252
Sending a Calendar event (iCalendar vEvent)
You want to send a nice Calendar event (.ics) that a client such as Outlook processes nicely?
Easy!
Produce a Calendar event String (manually or by using a library such as ical4j) and pass it to
the EmailBuilder.
See the test demo app included in the Simple Java Mail source for a working example.
// Create a Calendar with something like ical4j
Calendar icsCalendar = new Calendar();
icsCalendar.getProperties().add(new ProdId("-//Events Calendar//iCal4j 1.0//EN"));
icsCalendar.getProperties().add(Version.VERSION_2_0);
(..) // add attendees, organizer, end/start date and whatever else you need
// Produce calendar string
ByteArrayOutputStream bOutStream = new ByteArrayOutputStream();
new CalendarOutputter().output(icsCalendar, bOutStream);
String yourICalEventString = bOutStream.toString("UTF-8")
// Let Simple Java Mail handle the rest
currentEmailBuilder
.withCalendarText(CalendarMethod.REQUEST, yourICalEventString)
Direct access to the internal Session
For emergencies, you can also get a hold of the internal Session instance itself. You should never need this however and if you do it means Simple Java Mail failed to simplify the configuration process for you. Please let us know how we can help alleviate this need.
Mailer mailer = ...;
Session session = mailer.getSession();
// do your thing with session
Configure delivery / read receipt
For servers and clients that support it (mostly Outlook at offices), Simple Java Mail has built in support for
'delivery receipt' and 'read receipt', which is configured
through the headers Return-Receipt-To and Disposition-Notification-To respectively.
You can explicitly define the email address to return the receipts to or else Simple Java Mail will default to the replyTo address if available or else the fromAddress.
If you simply enabled Return-Receipt-To or Disposition-Notification-To without providing
an address, it will default to the first Reply-To recipient if provided or From recipient otherwise.
currentEmailBuilder.
.withDispositionNotificationTo();
.withReturnReceiptTo();
// or:
.withDispositionNotificationTo(new Recipient("name", "address@domain.com"));
.withReturnReceiptTo(new Recipient("name", "address@domain.com"));
Configure Delivery Status Notification
Delivery Status Notification (DSN) asks the SMTP server for delivery-status reports such as failure, delay or success notifications. This is separate from read receipts: DSN is about delivery status at SMTP/server level.
DSN can be configured per email, through mailer defaults or through properties.
Email email = EmailBuilder.startingBlank()
.from("sender@example.com")
.withRecipients(new RecipientBuilder()
.withAddress("receiver@example.com")
.withType(Message.RecipientType.TO)
.build())
.withPlainText("Hello")
.withDeliveryStatusNotification(
DeliveryStatusNotification.ReturnOption.HEADERS_ONLY,
DeliveryStatusNotification.NotifyOption.FAILURE,
DeliveryStatusNotification.NotifyOption.DELAY)
.buildEmail();
simplejavamail.defaults.delivery.status.notification.notify=FAILURE,DELAY
simplejavamail.defaults.delivery.status.notification.return.option=HEADERS_ONLY
Validating Email Addresses
Simple Java Mail can validate your email addresses. It's not just a simple regex check, but a complete and robust full validation against RFC-2822 and others. It does this by including JMail in the library.
Address validation is performed automatically when sending emails, but you can also directly perform validations.
See JMail for more examples and configurations.
currentMailerBuilder
.withEmailValidator(
JMail.strictValidator()
.requireOnlyTopLevelDomains(TopLevelDomain.DOT_COM)
.withRule(email -> email.localPart().startsWith("allowed"))
)
// or
.clearEmailValidator() // turn off validation
.resetEmailValidator() // reset to default (strict)
// you can also directly perform validations:
mailer.validate(email); // does all checks including address validation
// or just do the address validation
JMail.isValid("your_address@domain.com");
// or, fine-tuned to be stricter
JMail.strictValidator()
.isValid("your_address@domain.com");
Note: any email address validation behaviour you have defined will be overridden if you disable client-side validations completely (which also disables CRLF injection scanning).
currentMailerBuilder
.disablingAllClientValidation(true)
// or
.resetDisableAllClientValidations() // reset to default (false)
Converting between, Email, MimeMessage, EML and Outlook .msg
With Simple Java Mail you can easily convert between email types. This includes reading S/MIME protected emails from file.
For example, if you need a MimeMessage, you can convert Email objects, EML data and even Outlook .msg files.
If you already have a MimeMessage, you can convert it into an Email instance, complete with embedded images and attachments (or just the metadata), headers intact.
You can even build a mass Outlook .msg to EML converter if you like!
To enable Outlook message parsing support, include the outlook-module. To enable S/MIME signed content and decryption support, include the smime-module
/*
* Most conversion methods support an optional Pkcs12Config config for handling S/MIME
*/
// from Email
String eml = EmailConverter.emailToEML(yourEmail);
MimeMessage mimeMessage = EmailConverter.emailToMimeMessage(yourEmail);
MimeMessage mimeMessage = EmailConverter.emailToMimeMessage(yourEmail, yourSession);
// from MimeMessage
Email email = EmailConverter.mimeMessageToEmail(yourMimeMessage);
String eml = EmailConverter.mimeMessageToEML(yourMimeMessage);
// from EML
Email email = EmailConverter.emlToEmail(emlDataString);
MimeMessage mimeMessage = EmailConverter.emlToMimeMessage(emlDataString);
MimeMessage mimeMessage = EmailConverter.emlToMimeMessage(emlDataString, yourSession);
// from Outlook .msg
Email email = EmailConverter.outlookMsgToEmail(readToString("yourMessage.msg"));
Email email = EmailConverter.outlookMsgToEmail(new File("yourMessage.msg"));
Email email = EmailConverter.outlookMsgToEmail(getInputStream("yourMessage.msg"));
String eml = EmailConverter.outlookMsgToEML(readToString("yourMessage.msg"));
String eml = EmailConverter.outlookMsgToEML(new File("yourMessage.msg"));
String eml = EmailConverter.outlookMsgToEML(getInputStream("yourMessage.msg"));
MimeMessage mimeMessage = EmailConverter.outlookMsgToMimeMessage(readToString("yourMessage.msg"));
MimeMessage mimeMessage = EmailConverter.outlookMsgToMimeMessage(new File("yourMessage.msg"));
MimeMessage mimeMessage = EmailConverter.outlookMsgToMimeMessage(getInputStream("yourMessage.msg"));
Pkcs12Config myKeyInfo = Pkcs12Config.builder()
.pkcs12Store("my_smime_keystore.pkcs12")
.storePassword("my_store_password")
.keyAlias("my_key_alias")
.keyPassword("my_key_password")
.build();
Email decryptedEmail = EmailConverter.emlToEmail(emlDataString, myKeyInfo);
Email decryptedEmail = EmailConverter.mimeMessageToEmail(yourMimeMessage, myKeyInfo);
Email decryptedEmail = EmailConverter.mimeMessageToEmail(yourMimeMessage, myKeyInfo, /*fetchAttachments*/ false);
emailBuilder = EmailConverter.mimeMessageToEmailBuilder(yourMimeMessage, /*Pkcs12Config*/ null, /*fetchAttachments*/ true);
When converting Outlook .msg files, use the result API if you need to inspect source-specific Outlook data.
The converted email keeps only meaningful email headers; structural Outlook source headers stay available through
OutlookMessageData.
OutlookEmailConversionResult result =
EmailConverter.outlookMsgToEmailBuilderWithOutlookData(msgFile);
Email email = result.buildEmail();
OutlookMessageData outlookData = result.getOutlookMessageData();
List<String> receivedHeaders = outlookData.getHeaderValues("Received");
String messageClass = outlookData.getMessageClass();
String rawHeaders = outlookData.getRawHeaders();
Outlook conversion is lenient about unsupported encrypted S/MIME payloads. For signed S/MIME content, parsed email content is preserved
even when signature validation fails; check email.getOriginalSmimeDetails().getSmimeSignatureValid() when
the validation result matters.
Setting custom recipient for bouncing emails
For bouncing emails, you can provide a hint to the SMTP server to which bouncing emails should be returned.
This is also known as the Return-Path or Envelope FROM and is set on the Session instance with the property
mail.smtp.from.
Simple Java Mail offers a convenience method to set this property.
// in similar fashion to setting replyTo address:
currentEmailBuilder
.withBounceTo(aRecipientInstance) // or
.withBounceTo("Bob", "bob.techdesk@candyshop.com")
// or using one of the many alternative methods...
Replying to and forwarding emails
If you have an email you want to reply to or wish to forward, the EmailBuilder
has you covered.
Replying to an email:
EmailBuilder
.replyingTo(receivedEmail) // Email or MimeMessage
.from("dummy@domain.com")
.prependText("Reply body. Original email included below")
.buildEmail();
Forwarding an email:
EmailBuilder
.forwarding(receivedEmail) // Email or MimeMessage
.from("dummy@domain.com")
.withPlainText("Hello? This is Forward. See below email:")
.buildEmail();
Send using a proxy
Simple Java Mail supports sending emails through a proxy. It is also the only java mailing framework in the world that supports sending emails through authenticated proxies. The reason for this is that the underlying native Jakarta Mail framework supports anonymous SOCKS5 proxies, but not authenticated proxies.
To make this work with authentication, Simple Java Mail uses a trick: it sets up a temporary anonymous proxy server for Jakarta Mail to connect to and then the bridge relays the connection to the target proxy performing the authentication outside of Jakarta Mail.
This temporary server is referred to as the Proxy Bridging Server.
// anonymous proxy
currentMailerBuilder.withProxy("proxy.host.com", 1080)
// authenticated proxy
currentMailerBuilder.withProxy("proxy.host.com", 1080, "proxy username", "proxy password");
Refer to the configuration section on how to set proxy server defaults and the port on which the proxy bridge runs.
Testing a server connection
If you just want to do a connection test using your current configuration, including transport strategy and (authenticated) proxy, Simple Java Mail got you covered.
The connection test can also be done asynchronously and the result can be handled asynchronously as well. Take a look at Handling asynchronous mailing result.
If the mailer was configured with async(), no-arg
testConnection() uses that async default. Use
testConnection(false) when you need a blocking test.
// configure your mailer
Mailer mailer = ...;
// perform connection test
mailer.testConnection(); // uses the mailer's async default
mailer.testConnection(false); // explicitly blocking
mailer.testConnection(true); // explicitly asynchronous
Serializing Email objects
Simple Java Mail supports native Java serialization for Email objects with the following caveats:
- AttachmentResource.dataSource of type DataSource is transient
- Email.emailToForward of type MimeMessage is transient
- Email.dkimPrivateKeyInputStream of type InputStream is transient
- Email.smimeSigningConfig of type SmimeSigningConfig is transient
Plug your own sending logic
You want to benefit from Simple Java Mail's powerful features, but want to replace the actual
server testing and email sending with your own?
Simple Java Mail's got your back. Just
Define your own CustomMailer and plug it in.
The benefit of this is that Simple Java Mail acts as an accelerator, providing thread pool, applying email content-validation, address validations, configuring a Session instance, producing a MimeMessage, all with full S/MIME, DKIM support and everything else.
Send mail using MailGun REST API:
Mailer mailGunMailer = MailerBuilder
.withCustomMailer(new MailGunMailer())
.(..)
.buildMailer();
public class MailGunMailer implements CustomMailer {
@Override
public void testConnection(OperationalConfig operationalConfig, Session session) {
// call MailGun rest service to test the provided config
}
@Override
public void sendMessage(OperationalConfig operationalConfig, Session session, Email email, MimeMessage message) {
// call MailGun rest service to send the email!
}
}
Limit the maximum email size
Do you know your server's maximum allowed email size? Then it might be helpful to have Simple Java Mail reject emails that exceed this before trying to send them.
The following throws an EmailTooBig exception as the cause in a parent MailerException instance
Mailer mailer = MailerBuilder
.(..)
.withMaximumEmailSize(4) // 4 bytes, that's not much
.buildMailer();
try {
mailer.sendMail(emailBiggerThan4Bytes)
} catch(Exception e) {
// cause: EmailTooBigException
// msg: "Email size of 277 bytes exceeds maximum allowed size of 4 bytes"
e.getCause().printStrackTrace();
}