send-template-email.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. module.exports = {
  2. friendlyName: 'Send template email',
  3. description: 'Send an email using a template.',
  4. extendedDescription:
  5. `To ease testing and development, if the provided "to" email address ends in "@example.com",
  6. then the email message will be written to the terminal instead of actually being sent.
  7. (Thanks [@simonratner](https://github.com/simonratner)!)`,
  8. inputs: {
  9. template: {
  10. description: 'The relative path to an EJS template within our `views/emails/` folder -- WITHOUT the file extension.',
  11. extendedDescription:
  12. `Use strings like "foo" or "foo/bar", but NEVER "foo/bar.ejs". For example, "marketing/welcome" would send an email
  13. using the "views/emails/marketing/welcome.ejs" template.`,
  14. example: 'reset-password',
  15. type: 'string',
  16. required: true
  17. },
  18. templateData: {
  19. description: 'A dictionary of data which will be accessible in the EJS template.',
  20. extendedDescription:
  21. `Each key will be a local variable accessible in the template. For instance, if you supply
  22. a dictionary with a \`friends\` key, and \`friends\` is an array like \`[{name:"Chandra"}, {name:"Mary"}]\`),
  23. then you will be able to access \`friends\` from the template:
  24. \`\`\`
  25. <ul><% for (friend of friends){ %>
  26. <li><%= friend.name %></li><% }); %></ul>
  27. \`\`\`
  28. This is EJS, so use \`<%= %>\` to inject the HTML-escaped content of a variable,
  29. \`<%= %>\` to skip HTML-escaping and inject the data as-is, or \`<% %>\` to execute
  30. some JavaScript code such as an \`if\` statement or \`for\` loop.`,
  31. type: {},
  32. defaultsTo: {}
  33. },
  34. to: {
  35. description: 'The email address of the primary recipient.',
  36. extendedDescription:
  37. `If this is any address ending in "@example.com", then don't actually deliver the message.
  38. Instead, just log it to the console.`,
  39. example: 'foo@bar.com',
  40. required: true
  41. },
  42. subject: {
  43. description: 'The subject of the email.',
  44. example: 'Hello there.',
  45. defaultsTo: ''
  46. },
  47. layout: {
  48. description:
  49. 'Set to `false` to disable layouts altogether, or provide the path (relative '+
  50. 'from `views/layouts/`) to an override email layout.',
  51. defaultsTo: 'layout-email',
  52. custom: (layout)=>layout===false || _.isString(layout)
  53. }
  54. },
  55. exits: {
  56. success: {
  57. outputFriendlyName: 'Email delivery report',
  58. outputDescription: 'A dictionary of information about what went down.',
  59. outputType: {
  60. loggedInsteadOfSending: 'boolean'
  61. }
  62. }
  63. },
  64. fn: async function(inputs, exits) {
  65. var path = require('path');
  66. var url = require('url');
  67. var util = require('util');
  68. if (!_.startsWith(path.basename(inputs.template), 'email-')) {
  69. sails.log.warn(
  70. 'The "template" that was passed in to `sendTemplateEmail()` does not begin with '+
  71. '"email-" -- but by convention, all email template files in `views/emails/` should '+
  72. 'be namespaced in this way. (This makes it easier to look up email templates by '+
  73. 'filename; e.g. when using CMD/CTRL+P in Sublime Text.)\n'+
  74. 'Continuing regardless...'
  75. );
  76. }
  77. if (_.startsWith(inputs.template, 'views/') || _.startsWith(inputs.template, 'emails/')) {
  78. throw new Error(
  79. 'The "template" that was passed in to `sendTemplateEmail()` was prefixed with\n'+
  80. '`emails/` or `views/` -- but that part is supposed to be omitted. Instead, please\n'+
  81. 'just specify the path to the desired email template relative from `views/emails/`.\n'+
  82. 'For example:\n'+
  83. ' template: \'email-reset-password\'\n'+
  84. 'Or:\n'+
  85. ' template: \'admin/email-contact-form\'\n'+
  86. ' [?] If you\'re unsure or need advice, see https://sailsjs.com/support'
  87. );
  88. }//•
  89. // Determine appropriate email layout and template to use.
  90. var emailTemplatePath = path.join('emails/', inputs.template);
  91. var layout;
  92. if (inputs.layout) {
  93. layout = path.relative(path.dirname(emailTemplatePath), path.resolve('layouts/', inputs.layout));
  94. } else {
  95. layout = false;
  96. }
  97. // Compile HTML template.
  98. // > Note that we set the layout, provide access to core `url` package (for
  99. // > building links and image srcs, etc.), and also provide access to core
  100. // > `util` package (for dumping debug data in internal emails).
  101. var htmlEmailContents = await sails.renderView(
  102. emailTemplatePath,
  103. Object.assign({layout, url, util }, inputs.templateData)
  104. )
  105. .intercept((err)=>{
  106. err.message =
  107. 'Could not compile view template.\n'+
  108. '(Usually, this means the provided data is invalid, or missing a piece.)\n'+
  109. 'Details:\n'+
  110. err.message;
  111. return err;
  112. });
  113. // Sometimes only log info to the console about the email that WOULD have been sent.
  114. // Specifically, if the "To" email address is anything "@example.com".
  115. //
  116. // > This is used below when determining whether to actually send the email,
  117. // > for convenience during development, but also for safety. (For example,
  118. // > a special-cased version of "user@example.com" is used by Trend Micro Mars
  119. // > scanner to "check apks for malware".)
  120. var isToAddressConsideredFake = Boolean(inputs.to.match(/@example\.com$/i));
  121. // If that's the case, or if we're in the "test" environment, then log
  122. // the email instead of sending it:
  123. if (sails.config.environment === 'test' || isToAddressConsideredFake) {
  124. sails.log(
  125. `Skipped sending email, either because the "To" email address ended in "@example.com"
  126. or because the current \`sails.config.environment\` is set to "test".
  127. But anyway, here is what WOULD have been sent:
  128. -=-=-=-=-=-=-=-=-=-=-=-=-= Email log =-=-=-=-=-=-=-=-=-=-=-=-=-
  129. To: ${inputs.to}
  130. Subject: ${inputs.subject}
  131. Body:
  132. ${htmlEmailContents}
  133. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-`);
  134. } else {
  135. // Otherwise, we'll check that all required Mailgun credentials are set up
  136. // and, if so, continue to actually send the email.
  137. if (!sails.config.custom.mailgunSecret || !sails.config.custom.mailgunDomain) {
  138. throw new Error(`Cannot deliver email to "${inputs.to}" because:
  139. `+(()=>{
  140. let problems = [];
  141. if (!sails.config.custom.mailgunSecret) {
  142. problems.push(' • Mailgun secret is missing from this app\'s configuration (`sails.config.custom.mailgunSecret`)');
  143. }
  144. if (!sails.config.custom.mailgunDomain) {
  145. problems.push(' • Mailgun domain is missing from this app\'s configuration (`sails.config.custom.mailgunDomain`)');
  146. }
  147. return problems.join('\n');
  148. })()+`
  149. To resolve these configuration issues, add the missing config variables to
  150. \`config/custom.js\`-- or in staging/production, set them up as system
  151. environment vars. (If you don\'t have a Mailgun domain or secret, you can
  152. sign up for free at https://mailgun.com to receive sandbox credentials.)
  153. > Note that, for convenience during development, there is another alternative:
  154. > In lieu of setting up real Mailgun credentials, you can "fake" email
  155. > delivery by using any email address that ends in "@example.com". This will
  156. > write automated emails to your logs rather than actually sending them.
  157. > (To simulate clicking on a link from an email, just copy and paste the link
  158. > from the terminal output into your browser.)
  159. [?] If you're unsure, visit https://sailsjs.com/support`
  160. );
  161. }
  162. await sails.helpers.mailgun.sendHtmlEmail.with({
  163. htmlMessage: htmlEmailContents,
  164. to: inputs.to,
  165. subject: inputs.subject,
  166. testMode: false
  167. });
  168. }//fi
  169. // All done!
  170. return exits.success({
  171. loggedInsteadOfSending: isToAddressConsideredFake
  172. });
  173. }
  174. };