Sending email from your Apostrophe project
Any module in Apostrophe can send email by calling its own self.email()
method and the popular nodemailer package. However, prior to sending an email using this method, either the nodemailer
option of the @apostrophecms/email
module needs to be configured, or another Nodemailer transport needs to be defined as self.transport
in that same module. The nodemailer
option will pass any values to the createTransport
method of the nodemailer app.
Due to the difficulty of ensuring that emails are delivered reliably, we recommend that you utilize a delivery provider. However, if your server is set up with both DKIM and SPF (DMARC) configured correctly, you can easily use the sendmail
server app.
Configuring to use sendmail
If your server has sendmail
installed, nodemailer
can use the service to send emails by setting the sendmail
property to true
. Both the newline
and path
properties may also need to be configured. By default, newline
is set to unix
(<LF> only), but it also accepts windows
(<LF> and <CR>). The path
property defaults to usr/bin/sendmail
. If this isn't how your server is configured, then the correct path needs to be passed in.
Example
Configuring using generic SMTP
Using a 3rd-party email delivery provider like Gmail, or Mailgun will probably better guarantee delivery without the email being either bounced or sent to spam. Most providers use SMTP. In fact, the sendmail
server application uses SMTP. It just performs most of the configuration for you automatically. To configure sending through other providers, you typically need to supply three different parameters to the nodemailer
option through an object - the 'host', 'port', and 'auth' parameters. Depending on the host there are a number of other options that could be passed as outlined in the nodemailer
documentation.
Example
INFO
When using Gmail as the SMTP email relay, you need to make sure that the "from" address of your email matches the user name added to the nodemailer configuration. You will likely also have to allow less secure apps and disable Captcha from your Google account dashboard.
Using specific service APIs
The nodemailer
app has four built-in transports - sendmail
, SES
- for sending by AWS SES, and the stream
and jsonTransport
transports - which are used for testing or passing data to another nodemailer extension for processing as detailed in the nodemailer documentation. You can create your own custom transport using the nodemailer
transport API documentation, or use one of the many transport plugins that are available. These can either be found on the provider's site or by searching NPM. Each transporter will have slightly different parameters configured through an object that is then passed along with the specific transport.
Example for Mailgun
Sending email from a module
Once the @apostrophecms/email
module is configured, email can be sent from any module using the self.email(req, template, data, options)
method.
The first parameter passed to this method is the req
.
The next parameter, template
, takes the name of a Nunjucks template that will make up the body of the email. This template should be located in the views
template of the module. The method will pass this HTML template, as well as an automatically generated plain text version, to the nodemailer
transport object.
The data
parameter takes an object that will be passed to the Nunjucks template for populating any customized fields. It can be accessed through data.property
within the template.
The final parameter, options
, should be an object that contains the information for the email header. This is typically from
, to
, and subject
. The from
parameter can alternatively be given a default by setting the { email: { from: 'some@address.com' } }
option in the module that is calling self.email
, or by setting the from: 'some@address.com'
option (not nested in a parent option) on the @apostrophecms/email
module itself. The from
address used will then be (1) the from
property given in the final parameter to sendEmail
, or (2) the from
subproperty of the email
option configured on the module sending the email, or (3) the from
option configured on the @apostrophecms/email
module, in that order.
Example usage setting from
in sendEmail
In this example, we are creating a custom piece type that implements an article. The afterSave
server event property is being added to the handlers()
function. This event is emitted any time this custom module saves a new article and runs the function that is being passed in as a value. This function, in turn, sends out an email to the site editors. The data coming from the piece is passed into the email.html
template through the data
argument to add the title and blurb for the editors to review.
Example usage setting from
in the module configuration
In this example, we are setting a unique from
address for the e-mail that is sent to users when they want to reset their login password. This address will override any address set in the @apostrophecms/email
options. Note, that if you pass a from
property in the options
object of sendEmail()
it will *not be overridden by this module option.
Debugging email delivery without sending
The self.email()
method returns info
. This can be used to determine if message handoff to the mailing service has been completed successfully. Note: this does not mean that message delivery will complete successfully. The email could still be rejected en route or by the receiving server.
The returned data can also be used along with the stream
transporter to ensure that the header and body of the email have been correctly constructed without sending the email.
Stream example
Triggering email from a route
In addition to using handlers()
to trigger email delivery, you can use apiRoutes()
. This can be triggered by any selected HTTP request, like submission of FormData through POST, or retrieval of a specified payload with GET. All of the same arguments passed when invoking the email()
method from a module need to be supplied when using the method in a route.
Example usage
So, what is going on with this code? First, we are passing our apiRoutes()
a post
object. This contains the functions that should be used with a POST
HTTP request. Each expected route should get a separate function. In this case, we are passing the subscribe
function. This will monitor for a POST request to https://www.mysite.com/api/v1/subscribe
. We could give the property a name prefixed with a slash to monitor that exact route - https://www.mysite.com/subscribe
. See the reference documentation for more details.
In the next block of code, we test to ensure that the information needed to construct the options
and data
arguments exists in the submission. If it does exist, the values for both those parameters are created with sanitization using self.apos.launder.string()
. Finally, this information and the email template are passed to the self.email()
method in a try block. If passing the email to the handler fails, the exception will be caught automatically, logged, and reported as a 500 error. Note that even if the self.email()
method doesn't throw an error it does not mean that message delivery will be completed successfully. The email could still be rejected en route or by the receiving server.