Mail Sender API
API to send emails |
Type | JAR |
Category | API |
Developed by | |
Rating | |
License | GNU Lesser General Public License 2.1 |
Bundled With | XWiki Standard |
Compatibility | First introduced in XWiki 6.1M2 and heavily modified in versions 6.4M3, 6.4.2, 7.0M2, 6.4.4, 7.1M1, 6.4.5, 7.1M2 and 7.1RC1. |
Table of contents
Description
This API allows to:
- Ability to send Multipart emails with any type of mime type content (html, text, vcalendar, etc)
- Ability to embed images in HTML emails
- Ability to send mail templates
- Ability to send mails to list of users, a list of groups and a list of emails (with ability to exclude users, groups and emails), ensuring that recipients don't get duplicate emails
- Scripting API to make it easy to send mails from wiki pages
- Asynchronous email sending
- Support for sending large volume of emails
There's also a Mail Application that provides an Administration UI for configuring parameters and to see the statuses of sent mails (when using the database Mail Listener - see below).
See also the Mail Sender Storage module which extends this API.
RFC Compliance
The XWiki Mail API uses the Jakarta Mail API as its implementation, and thus supports the following RFCs:
API
Most API examples below are written in Velocity. If you're using this module from Java make sure to check the Java API Example below.
Create a Message
// Note that "from" is optional and if not specified, taken from Mail configuration, in the following order:
// - first a ##from## xproperty is looked for in a ##XWiki.SendMailConfigClass## xobject in the ##XWiki.MailConfig## page in the current wiki
// - if not found, an ##admin_email## xproperty is looked for in the ##WebPreferences## page for the current space
// - if not found, an ##admin_email## xproperty is looked for in the ##XWiki.XWikiPreferences## page
// - if not found, a ##mail.sender.from## configuration property is looked for in ##xwiki.properties##
// - if not found, no from will be set and the sending will fail
$message = $services.mail.sender.createMessage()
$message = $services.mail.sender.createMessage(to, subject)
$message = $services.mail.sender.createMessage(from, to, subject)
Add Content
- Add Simple text:$message.addPart("text/plain", "text message")
- Add simple text to message with a mail header:$message.addPart("text", "text message", {"headers" : { "Content-Transfer-Encoding" : "quoted-printable"}})
- Add simple HTML to message:$message.addPart("text/html", "html message")
- Add HTML + alternate text to message:$message.addPart("text/html", "html message", {"alternate" : "text message"})
- Add HTML + alternate text + embedded images + some attachments to message (Note: $attachments is of type List<Attachment> here).$message.addPart("text/html", "html message", {"alternate" : "text message", "attachments" : $attachments})
Where $attachments is of type List<com.xpn.xwiki.api.Attachment>. Example to get an attachment:
#set ($attachment = $xwiki.getDocument('reference here').getAttachment('attachment name'))
#set ($attachments = [$attachment]) - Add HTML + alternate text from a Template Document containing a XWiki.Mail object. Any $var1 Velocity variable is replaced with value1.$message.addPart("xwiki/template", $documentReference, {"velocityVariables" : { "var1" : "value1" }})
- Same as previous addPart() example but also add internationalization support by retrieving the XWiki.Mail Object which has a language property equals to fr.$message.addPart("xwiki/template", $documentReference, {"language" : "fr", "velocityVariables" : { "var1" : "value1" }})
- Add HTML + alternate text + embedded images + some attachments, from a Template Document containing a XWiki.Mail object. Any $var1 Velocity variable is replaced with value1. (Note: $attachments is of type List<Attachment> here).$message.addPart("xwiki/template", $documentReference, {"velocityVariables" : { "var1" : "value1" }, "attachments" : $attachments})
- Same as previous addPart() example but includes all attachments found in the Mail Template document instead of controlling precisely which attachments to send. Note that if you also pass "attachments" : $attachments it'll not include by default the attachments found in the template.$message.addPart("xwiki/template", $documentReference, {"includeTemplateAttachments" : true, "velocityVariables" : { "var1" : "value1" })
Note: addPart() returns a BodyPart object which can be used to set/get headers.
Send the Messages
- Send a single Message, synchronously (it'll block till the mail is sent or it fails), storing the result in memory:#set ($mailResult = $services.mail.sender.send($message))
- Send N messages, synchronously, storing the result in memory:#set ($mailResult = $services.mail.sender.send([$message1, $message2, ...]))
// Equivalent to:
#set ($mailResult = $services.mail.sender.send([$message1, $message2, ...], 'memory')) - Send N message, asynchronously, storing the result in memory:#set ($mailResult = $services.mail.sender.sendAsynchronously([$message1, $message2, ...], 'memory'))
- Send N messages, asynchronously, storing the results in the database. It can then be retrieved later on. The Mail Application has an Admin screen which lists the statuses of all mails sent like this. Note that this very useful especially when sending large volume of emails, to see which mails have succeeded and which mails have failed to be sent.#set ($mailResult = $services.mail.sender.sendAsynchronously([$message1, $message2, ...], 'database'))
Set the Type
Sets the type of email that is being sent. This allows (for example) to filter these emails in the Mail Sender Status Admin UI (when using a Database Mail Listener). Example of types: "Watchlist", "Reset Password", "Send Page by Mail", etc.
Check Message Statuses
When mails are sent asynchronously, it's possible to check the status of the sending process by calling:
$mailResult.statusResult.isProcessed()
// Wait 10 seconds till the mails are sent (the passed timeout is expressed in milliseconds)
$mailResult.statusResult.waitTillProcessed(10000L)
Check for Errors
- Checking for errors that can occur before the mails have been processed:#if ($services.mail.sender.lastError)
{{error}}$exceptiontool.getStackTrace($services.mail.sender.lastError){{/error}}
#endThis can happen for example under the following conditions:
- If an error happened when creating the message(s) (when using the $services.mail.sender.createMessage(...) APIs)
- If there isn't enough permission to send mail (for example if the page containing the sending script doesn't have Programming Rights)
- If the Mail Listener referenced by the second parameter of $services.mail.sender.send(messages, mailListenerHint) doesn't exist
- Checking for mails sent successfully:#set ($mailStatuses = $mailResult.statusResult.getByState('SENT'))
#foreach ($mailStatus in $mailStatuses)
* Mail ($mailStatus.messageId) - Date Sent: $mailStatus.date
#end - Checking for all mail statuses:#set ($mailStatuses = $mailResult.statusResult.getAll())
#foreach ($mailStatus in $mailStatuses)
* Mail ($mailStatus.messageId) - Date Sent: $mailStatus.date State: $mailStatus.state - Error: $mailStatus.errorSummary
#end- Checking for mails that have failed to be sent:#set ($mailStatuses = $mailResult.statusResult.getByState('SEND_ERROR'))
#foreach ($mailStatus in $mailStatuses)
{{error}}
$mailStatus.errorSummary
$mailStatus.errorDescription
{{/error}}
#end
- Checking for mails that have failed to be prepared:#set ($mailStatuses = $mailResult.statusResult.getByState('PREPARE_ERROR'))
#foreach ($mailStatus in $mailStatuses)
{{error}}
$mailStatus.errorSummary
$mailStatus.errorDescription
{{/error}}
#end
- Checking for all mail statuses in error:#set ($mailStatuses = $mailResult.statusResult.getAllErrors())
#foreach ($mailStatus in $mailStatuses)
* Mail ($mailStatus.messageId) - Date Sent: $mailStatus.date State: $mailStatus.state - Error: $mailStatus.errorSummary
#end
Accessing Configuration
Access the Mail Sending configuration. In this example we define a default from email address if no from is defined on the configuration:
#set ($from = $services.mail.sender.configuration.fromAddress)
#if ("$!from" == '')
#set ($from = "no-reply@${request.serverName}")
#endMimeBodyPartFactory Implementations
When adding a body part using the addPart(mimeType, source, ...) script API, the following logic is used:
- Look for a Component implementing MimeBodyPartFactory and using the passed mimeType as a Hint.
- If no Component is found and the source is a String then defaults to using the default MimeBodyPartFactory implementation
- Otherwise throws an exception.
The following implementations are available:
- default: Creates a text Message Body Part.
- text/html: Creates an HTML BodyPart that supports a text alternative and a list of attachments that will be added to the mail as standard attachments and also as embedded images if they are referenced in the passed HTML using the format <img src="cid:(attachment name)"/>.
- xwiki/attachment: Creates an attachment Body Part from an Attachment object.
- xwiki/template: Creates an Body Part from a Document Reference pointing to a Document containing an XWiki.Mail XObject (the first one found is used). Note that when evaluating Velocity in Mail Templates, the Execution Context used is a clone of the one that was available when the send*(...) method was called. Thus all Velocity bindings that were present are available from your Mail Template (request, xwiki, services, Velocity Tools, etc).
Specialized Message Factories
There are also specialized Message factories that can be used to create pre-filled Message objects. For example it's possible to create a message having its subject automatically computed from a template (i.e. from a wiki page having a XWiki.Mail object), by evaluating its subject xproperty with Velocity (see the example further below for more details) + having a template body part added too. Generic API:
#set ($message = $services.mail.sender.createMessage(hint, source, parameters))
#set ($messages = $services.mail.sender.createMessages(hint, source, parameters))Where:
- hint is the Component hint of the component implementing the org.xwiki.mail.MimeMessageFactory role.
- source depends on the hint used. For example when the hint is template, the source represents the Document Reference to a page containing an XWiki.Mail object.
- parameters depends on the hint used too. For example when the hint is template, it's used to pass Velocity variables and values to use when evaluating the subject xproperty of the XWiki.Mail object.
Check the examples below to learn more.
Using from Java
Spirit: The JavaMail API should be used and XWiki only provides some helper components to make it simpler to use.
Example:
@Inject MailSenderConfiguration configuration;
@Inject @Named("text/html") MimeBodyPartFactory htmlPartFactory;
@Inject MailSender mailSender;
@Inject @Named("database") MailListener databaseMailListener;
// Step 1: Create a JavaMail Session
//... with authentication:
Session session = Session.getInstance(configuration.getAllProperties(), new XWikiAuthenticator(configuration));
//... without authentication:
Session session = Session.getInstance(configuration.getAllProperties());
// Step 2: Create the Message to send
MimeMessage message = new MimeMessage(session);
message.setSubject("subject");
message.addRecipient(MimeMessage.RecipientType.TO, new InternetAddress("john@doe.com"));
// Step 3: Add the Message Body
Multipart multipart = new MimeMultipart("mixed");
// Add HTML in the body, with a text alternative and attachments
Map<String, Object> parameters = new HashMap<>();
parameters.put("alternative", "text");
parameters.put("attachments", attachments);
multipart.addBodyPart(htmlPartFactory.create("some html here", parameters));
message.setContent(multipart);
// Step 4: Send the mail
// Synchronously
MailResult result = mailSender.send(Arrays.asList(message), session);
// Asynchronously with a Database Mail Listener:
MailResult result = mailSender.sendAsynchronously(Arrays.asList(message), session, databaseMailListener);
// Optional: Block till there are no more messages on the sending queue.
// Parameter passed is the max time to wait in milliseconds.
result.getStatusResult().waitTillProcessed(10000L);Get standard Session
It's also possible to directly create a pre-configured mail Session.
@Inject SessionFactory sessionFactory;
@Inject @Named("text/html") MimeBodyPartFactory htmlPartFactory;
@Inject MailSender mailSender;
@Inject @Named("database") MailListener databaseMailListener;
// Step 1: Create a JavaMail Session
Map<String, String> customConfiguration = new HashMap()
Session session = sessionFactory.create(customConfiguration);Using from Groovy
Example of sending an HTML email with an attachment, using Groovy (can be useful for example if you need to write Groovy for a scheduler job):
{{groovy}}
def from = 'from@mydomain.com'
def to = 'to@somedomain.com'
def message = services.mail.sender.createMessage(from, to, 'test')
def attachment = xwiki.getDocument('Sandbox.WebHome').getAttachment('XWikiLogo.png')
def attachments = [attachment]
def parameters = ['alternative': 'text', 'attachments': attachments]
message.addPart('text/html', '<h1>html title</h1>', parameters)
def result = services.mail.sender.send(message)
result.statusResult.waitTillProcessed(10000L)
{{/groovy}}Examples
In addition to the examples below you can also watch a contributed video showing how to configure mail sending and testing it.
Example 1: Send a simple text email
This example uses a memory Mail Listener.
{{velocity}}
#set ($message = $services.mail.sender.createMessage("localhost@xwiki.org", "john@doe.com", "subject"))
#set ($discard = $message.addPart("text/plain", "text content"))
#set ($mailResult = $services.mail.sender.send($message))
## Check if the message was created properly and if we have permissions to send emails
#if ($services.mail.sender.lastError)
{{error}}$exceptiontool.getStackTrace($services.mail.sender.lastError){{/error}}
#end
## Check if the mail we tried to send has failed to be sent
#set ($statuses = $mailResult.statusResult.getAllErrors())
#if ($statuses.hasNext())
#set ($status = $statuses.next())
{{error}}
Error: $status.errorSummary ($status.state)
$status.errorDescription
{{/error}}
#end
{{/velocity}}The same example using a database Mail Listener.
{{velocity}}
#set ($message = $services.mail.sender.createMessage("localhost@xwiki.org", "john@doe.com", "subject"))
#set ($discard = $message.addPart("text/plain", "text content"))
#set ($mailResult = $services.mail.sender.send([$message], 'database'))
## Check if the message was created properly and if we have permissions to send emails
#if ($services.mail.sender.lastError)
{{error}}$exceptiontool.getStackTrace($services.mail.sender.lastError){{/error}}
#end
## Check if the mail we tried to send has failed to be sent
#set ($statuses = $mailResult.statusResult.getAllErrors())
#if ($statuses.hasNext())
#set ($status = $statuses.next())
{{error}}
Error: $status.errorSummary ($status.state)
$status.errorDescription
{{/error}}
#end
{{/velocity}}Example 2: Send a text + calendar event email
This example uses a memory Mail Listener.
{{velocity}}
#set ($message = $services.mail.sender.createMessage("localhost@xwiki.org", "john@doe.com", "subject"))
#set ($discard = $message.addPart("text/plain", "text content"))
#set ($discard = $message.addPart("text/calendar;method=CANCEL", "
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID: Meeting
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20140616T164100
DTSTART:20140616T164100
DTEND:20140616T194100
SUMMARY:test request
UID:324
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:john@doe.com
ORGANIZER:MAILTO:john@doe.com
LOCATION:on the net
DESCRIPTION:learn some stuff
SEQUENCE:0
PRIORITY:5
CLASS:PUBLIC
STATUS:CONFIRMED
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT00H15M00S
END:VALARM
END:VEVENT
END:VCALENDAR
", {"headers" : {"Content-Class" : "urn:content-classes:calendarmessage"}}))
#set ($mailResult = $services.mail.sender.send($message))
{{/velocity}}Example 3: Send a Template email
This example uses a memory Mail Listener.
Add HTML + alternate text from a Template Document containing a XWiki.Mail object. Use that template to generate both the mail subject and the mail content. Also pass the current language to support internationalization (the right XWiki.Mail object will be used). Any $var1 Velocity variable is replaced with value1.
{{velocity}}
#set ($templateReference = $services.model.createDocumentReference('', 'Space', 'MailTemplatePage'))
#set ($mailParameters = {'language' : $xcontext.language, 'velocityVariables' : { 'var1' : 'value1' }})
#set ($message = $services.mail.sender.createMessage('template', $templateReference, $mailParameters))
#set ($discard = $message.setFrom('localhost@xwiki.org'))
#set ($discard = $message.addRecipient('to', 'john@doe.com'))
#set ($mailResult = $services.mail.sender.send($message))
{{/velocity}}It's also possible to pass "to", "from", "cc" and "bcc" in the parameter list:
{{velocity}}
#set ($templateReference = $services.model.createDocumentReference('', 'Space', 'MailTemplatePage'))
#set ($mailParameters = {'from' : 'localhost@xwiki.org', 'to' : 'john@doe.com', 'language' : $xcontext.language, 'velocityVariables' : { 'var1' : 'value1' }})
#set ($message = $services.mail.sender.createMessage('template', $templateReference, $mailParameters))
#set ($mailResult = $services.mail.sender.send($message))
{{/velocity}}Example 4: Send a Template email to a list of Users and Groups
The following example will send a template email to all the users in the XWiki.MyGroup group + to the XWiki.User1 and XWiki.User2 users + to the john@doe.com email address.
Note that nested groups are handled (i.e. if the XWiki.MyGroup group contains other groups, all users of those other groups will also receive the template email)!
{{velocity}}
## Parameters for the 'template' MimeMessageFactory
#set ($templateParameters = {'type' : 'Some type', 'language' : $xcontext.language, 'velocityVariables' : { 'var1' : 'value1' }})
#set ($templateReference = $services.model.createDocumentReference('', 'Space', 'MailTemplatePage'))
#set ($parameters = {'hint' : 'template', 'parameters' : $templateParameters, 'source' : $templateReference})
#set ($groupReference = $services.model.createDocumentReference('', 'XWiki', 'MyGroup'))
#set ($user1Reference = $services.model.createDocumentReference('', 'XWiki', 'User1'))
#set ($user2Reference = $services.model.createDocumentReference('', 'XWiki', 'User2'))
#set ($source = {'groups' : [$groupReference], 'users' : [$user1Reference, $user2Reference], 'emails' : ['john@doe.com']})
#set ($messages = $services.mail.sender.createMessages('usersandgroups', $source, $parameters))
#set ($mailResult = $services.mail.sender.send($messages, 'database'))
{{/velocity}}It's also possible to exclude groups, users and email addresses:
#set ($source = {'groups' : [$groupReference], 'users' : [$user1Reference, $user2Reference], 'emails' : ['john@doe.com'], 'excludedUsers' : [], 'excludedEmails' : [], 'excludedGroups' : []})To try this out, here's a script that sends some mail to all registered users of a wiki. To use it, create a Admin.MailTemplate terminal page and add a XWiki.Mail xobject to it and put the following content in any page:
{{velocity}}
#if ("$!request.confirm" == '1')
#set ($templateParameters = {'type' : 'SendAll', 'language' : $xcontext.language })
#set ($templateReference = $services.model.createDocumentReference('', 'Admin', 'MailTemplate'))
#set ($parameters = {'hint' : 'template', 'parameters' : $templateParameters, 'source' : $templateReference })
#set ($groupReference = $services.model.createDocumentReference('', 'XWiki', 'XWikiAllGroup'))
#set ($source = {'groups' : [$groupReference]})
#set ($messages = $services.mail.sender.createMessages('usersandgroups', $source, $parameters))
#set ($mailResult = $services.mail.sender.send($messages, 'database'))
Mails are being sent. Check the status in the [[Admin>>path:$xwiki.getURL('XWiki.XWikiPreferences', 'admin', 'editor=globaladmin§ion=emailStatus')]].
#else
To send email to all users, [[click here>>||queryString='confirm=1']]
#end
{{/velocity}}Example 5: Send a prepared Mime Message to a list of Users and Groups
The following example is similar to the previous one, except that it use a fixed prepared mime message to send it to multiple users as separate independent message. (ie: all the users in the XWiki.MyGroup group).
{{velocity}}
## Create a mime message, the way you like it, adding any part you like, without recipient.
#set ($message = $services.mail.sender.createMessage('localhost@xwiki.org', null, 'SendMimeMessageToGroup'))
#set ($discard = $message.addPart('text/plain', 'text content'))
## Use the mime message cloning factory as message factory to duplicate the created message
#set ($parameters = {'hint' : 'message', 'source' : $message})
#set ($source = {'groups' : [$services.model.createDocumentReference('', 'XWiki', 'XWikiAllGroup')]})
#set ($messages = $services.mail.sender.createMessages('usersandgroups', $source, $parameters))
#set ($result = $services.mail.sender.send($messages, 'database'))
{{/velocity}}Configuration
Mail Sender configuration properties are searched in various places, using the following order:
- Look for a non-empty value in Mail.MailConfig in the current wiki
- Look for a non-empty value in Mail.MailConfig in the main wiki
- Look for a non-empty value in the xwiki properties file
XWiki 15.4+ If the SMTP host config property is overridden in a subwiki, then the credentials (username and password) must also be overridden (if not, they'll be considered empty). This is to allow anonymous credentials in subwikis when overriding the SMTP host.
To see all the possible configuration properties, check the xwiki.properties file (check the "Mail" section in that file).
When the Mail.MailConfig page doesn't exist or doesn't contain a Mail.SendMailConfigClass or a Mail.GeneralMailConfigClass, they'll be created, and filled with non-empty values from XWikiPreferences xobjects (for backward-compatibility). More precisely the values from (current space).XWikiPreferences will be checked first and then XWiki.XWikiPreferences from the current wiki.
Extending
There are 2 ways to extend the API:
- By implementing components that implement the MimeBodyPartFactory role.
- By implementing components that implement the MimeMessageFactory role.
Security
Any document using the Scripting API needs to have Programming Rights by default to be allowed to send emails. This is configurable through the xwiki.properties file:
#-# Defines which authorization checks are done when sending mails using the Mail Sender Script Service.
#-# Example of valid values:
#-# - "programmingrights": the current document must have Programming Rights
#-# - "alwaysallow": no check is performed. This is useful when running XWiki in a secure environment where we
#-# want to allow all users to be able to send emails through the Script Service.
#-# The default is:
# mail.sender.scriptServiceCheckerHint = programmingrightsThere's a pluggable permission checker used for checking if a mail should be sent, when using the Mail Sender Script Service. Two implementations are provided (see above) but you can also provide your own implementation by implementing the org.xwiki.mail.script.ScriptServicePermissionChecker component role:
@Role
@Unstable
public interface ScriptServicePermissionChecker
{
/**
* @param message the message to check for authorization
* @exception MessagingException if the message is not allowed to be sent
*/
void check(MimeMessage message) throws MessagingException;
}For example you could imagine implementing checks on the size of the email or who the recipients are, run the content of the mail through some filter, etc.
Q&A
How to create a Multipart alternative with both HTML and text?
You might be tempted to write:
$message.addPart("text/plain", "text message")
$message.addPart("text/html", "html message")"However this is not correct. You need to use the HTML Body Part Factory and pass the alternative in the parameters list as in:
$message.addPart("text/html", "html message", {"alternate" : "text message"})Said differently each call to addPart() adds a body part in a top level "mixed" Multipart.
Compatibility with the Mail Sender Plugin
Since this API replaces the old Mail Sender Plugin, users should be aware of the following difference in the mimetypes used for attachments added to emails (note that the Mime Types used can be configured by providing a Custom Tika Mime type configuration file):
File Extension Old Mail Sender Plugin Mail Sender API exe application/octet-stream application/x-dosexec class application/octet-stream application/java-vm ai application/postscript application/illustrator ppt application/powerpoint application/vnd.ms-powerpoint cgi application/x-httpd-cgi text/x-cgi skp application/x-koan application/vnd.koan skd application/x-koan application/vnd.koan skt application/x-koan application/vnd.koan skm application/x-koan application/vnd.koan mif application/x-mif application/vnd.mif tcl application/x-tcl text/x-tcl t application/x-troff text/troff tr application/x-troff text/troff roff application/x-troff text/troff man application/x-troff-man text/troff me application/x-troff-me text/troff ms application/x-troff-ms text/troff rpm audio/x-pn-realaudio-plugin application/x-rpm ra audio/x-realaudio audio/x-pn-realaudio xyz chemical/x-pdb chemical/x-xyz sgml text/x-sgml text/sgml sgm text/x-sgml text/sgml wrl x-world/x-vrml model/vrml vrml x-world/x-vrml model/vrml ics application/ics text/calendar - Checking for mails that have failed to be sent:
Prerequisites & Installation Instructions
We recommend using the Extension Manager to install this extension (Make sure that the text "Installable with the Extension Manager" is displayed at the top right location on this page to know if this extension can be installed with the Extension Manager).
You can also use the manual method which involves dropping the JAR file and all its dependencies into the WEB-INF/lib folder and restarting XWiki.
Dependencies
Dependencies for this extension (org.xwiki.platform:xwiki-platform-mail-send-api 16.10.1):
- com.sun.mail:jakarta.mail 1.6.7
- org.xwiki.commons:xwiki-commons-component-api 16.10.1
- org.bouncycastle:bcprov-jdk18on 1.79