mailgun.module 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <?php
  2. /**
  3. * @file
  4. * Provides integration with Mailgun's email sending API.
  5. */
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function mailgun_menu() {
  10. $items = array();
  11. $items['admin/config/system/mailgun'] = array(
  12. 'title' => 'Mailgun',
  13. 'description' => 'Configure Mailgun settings.',
  14. 'page callback' => 'drupal_get_form',
  15. 'page arguments' => array('mailgun_admin_settings'),
  16. 'access arguments' => array('administer mailgun'),
  17. 'file' => 'mailgun.admin.inc',
  18. );
  19. $items['admin/config/system/mailgun/settings'] = array(
  20. 'title' => 'Settings',
  21. 'type' => MENU_DEFAULT_LOCAL_TASK,
  22. 'weight' => 0,
  23. );
  24. $items['admin/config/system/mailgun/test'] = array(
  25. 'title' => 'Send test email',
  26. 'page callback' => 'drupal_get_form',
  27. 'page arguments' => array('mailgun_test_form'),
  28. 'access arguments' => array('administer mailgun'),
  29. 'description' => 'Send a test e-mail using the Mailgun API.',
  30. 'file' => 'mailgun.admin.inc',
  31. 'type' => MENU_LOCAL_TASK,
  32. 'weight' => 1,
  33. );
  34. return $items;
  35. }
  36. /**
  37. * Implements hook_permission().
  38. */
  39. function mailgun_permission() {
  40. return array(
  41. 'administer mailgun' => array(
  42. 'title' => t('Administer Mailgun'),
  43. 'description' => t('Perform administration tasks for the Mailgun e-mail sending service.'),
  44. "restrict access" => TRUE,
  45. ),
  46. );
  47. }
  48. /**
  49. * Implements hook_help().
  50. */
  51. function mailgun_help($path, $arg) {
  52. switch ($path) {
  53. case 'admin/config/system/mailgun':
  54. return '<p>' . t('See <a href="@url">documentation</a> for instructions on installing and configuring Mailgun.', array('@url' => url('https://www.drupal.org/node/2547591'))) . '</p>';
  55. break;
  56. case 'admin/config/system/mailgun/test':
  57. return '<p>' . t('Use this form to send a test e-mail to ensure you have correctly configured Mailgun.') . '</p>';
  58. break;
  59. }
  60. }
  61. /**
  62. * Implements hook_cron_queue_info().
  63. */
  64. function mailgun_cron_queue_info() {
  65. $queues = array();
  66. $queues['mailgun_queue'] = array(
  67. 'worker callback' => 'mailgun_send',
  68. 'time' => 60,
  69. );
  70. return $queues;
  71. }
  72. /**
  73. * Implements hook_mail().
  74. */
  75. function mailgun_mail($key, &$message, $params) {
  76. switch ($key) {
  77. case 'test':
  78. $message['subject'] = t('Mailgun test email');
  79. $message['body'] = $params['message'];
  80. if ($params['attachment']) {
  81. $message['params']['attachments'] = array(drupal_realpath('misc/druplicon.png'));
  82. }
  83. break;
  84. }
  85. }
  86. /**
  87. * Implements hook_libraries_info().
  88. */
  89. function mailgun_libraries_info() {
  90. $libraries['mailgun'] = array(
  91. 'name' => 'Mailgun PHP library',
  92. 'vendor url' => 'https://documentation.mailgun.com/wrappers.html#php',
  93. 'download url' => 'https://github.com/mailgun/mailgun-php/archive/v1.7.2.zip',
  94. 'path' => 'vendor',
  95. 'version arguments' => array(
  96. 'file' => 'src/Mailgun/Constants/Constants.php',
  97. // const SDK_VERSION = "1.7";
  98. 'pattern' => '/const SDK_VERSION = \"((\d+)\.(\d+))\";/',
  99. ),
  100. 'files' => array(
  101. 'php' => array('autoload.php'),
  102. ),
  103. );
  104. return $libraries;
  105. }
  106. /**
  107. * Get the Mailgun client to access Mailgun's endpoints.
  108. *
  109. * @param string $key
  110. * The Mailgun API key. Leave empty to use the API key saved in database.
  111. */
  112. function mailgun_get_client($key = '') {
  113. // Check if the Mailgun PHP library is installed.
  114. $library = libraries_load('mailgun');
  115. if (!$library['installed']) {
  116. watchdog('mailgun', 'Mailgun client initialization failed: Unable to load the Mailgun PHP library.', NULL, WATCHDOG_ERROR);
  117. return FALSE;
  118. }
  119. $key = (empty($key)) ? variable_get('mailgun_api_key', '') : $key;
  120. if (empty($key)) {
  121. watchdog('mailgun', 'Mailgun client initialization failed: Missing API key.', NULL, WATCHDOG_ERROR);
  122. return FALSE;
  123. }
  124. $client = new \Mailgun\Mailgun($key);
  125. return $client;
  126. }
  127. /**
  128. * Send an e-mail using the Mailgun API.
  129. *
  130. * @param array $mailgun_message
  131. * A Mailgun message array. Contains the following keys:
  132. * - from: The e-mail addressthe message will be sent from.
  133. * - to: The e-mail addressthe message will be sent to.
  134. * - subject: The subject of the message.
  135. * - text: The plain-text version of the message. Processed using check_plain().
  136. * - html: The original message content. May contain HTML tags.
  137. * - cc: One or more carbon copy recipients. If multiple, separate with commas.
  138. * - bcc: One or more blind carbon copy recipients. If multiple, separate with commas.
  139. * - o:tag: An array containing the tags to add to the message. See: https://documentation.mailgun.com/user_manual.html#tagging.
  140. * - o:campaign: The campaign ID this message belongs to. See: https://documentation.mailgun.com/user_manual.html#um-campaign-analytics.
  141. * - o:deliverytime: Desired time of delivery. Messages can be scheduled for a maximum of 3 days in the future. See: https://documentation.mailgun.com/api-intro.html#date-format.
  142. * - o:dkim: Boolean indicating whether or not to enable DKIM signatures on per-message basis.
  143. * - o:testmode: Boolean indicating whether or not to enable test mode. See: https://documentation.mailgun.com/user_manual.html#manual-testmode.
  144. * - o:tracking: Boolean indicating whether or not to toggle tracking on a per-message basis. See: https://documentation.mailgun.com/user_manual.html#tracking-messages.
  145. * - o:tracking-clicks: Boolean or string "htmlonly" indicating whether or not to toggle clicks tracking on a per-message basis. Has higher priority than domain-level setting.
  146. * - o:tracking-opens: Boolean indicating whether or not to toggle clicks tracking on a per-message basis. Has higher priority than domain-level setting.
  147. * - h:X-My-Header: h: prefix followed by an arbitrary value allows to append a custom MIME header to the message (X-My-Header in this case). For example, h:Reply-To to specify Reply-To address.
  148. * - v:my-var: v: prefix followed by an arbitrary name allows to attach a custom JSON data to the message. See: https://documentation.mailgun.com/user_manual.html#manual-customdata.
  149. *
  150. * @return bool
  151. * TRUE if the mail was successfully accepted, FALSE otherwise.
  152. */
  153. function mailgun_send($mailgun_message) {
  154. $client = mailgun_get_client();
  155. if (!$client) {
  156. return FALSE;
  157. }
  158. // Test mode
  159. if (variable_get('mailgun_test', FALSE)) {
  160. $mailgun_message['o:testmode'] = 'yes';
  161. }
  162. // Merge the $mailgun_message array with options.
  163. $mailgun_message += $mailgun_message['params'];
  164. unset($mailgun_message['params']);
  165. if (variable_get('mailgun_domain', '_sender') == '_sender') {
  166. // Extract the domain from the sender's email address. Use regular expression to check since it could be either a plain email address or in the form "Name <example@example.com>".
  167. $tokens = (preg_match('/^\s*(.+?)\s*<\s*([^>]+)\s*>$/', $mailgun_message['from'], $matches) === 1) ? explode('@', $matches[2]) : explode('@', $mailgun_message['from']);
  168. $mail_domain = array_pop($tokens);
  169. // Retrieve a list of available domains first.
  170. $domains = array();
  171. try {
  172. $result = $client->get('domains');
  173. if ($result->http_response_code == 200) {
  174. foreach ($result->http_response_body->items as $item) {
  175. $domains[$item->name] = $item->name;
  176. }
  177. }
  178. else {
  179. watchdog('mailgun', 'Mailgun server returned a %code error. Could not retrieve domain list.', array('%code' => $result->http_response_code), WATCHDOG_ERROR);
  180. }
  181. } catch (Exception $e) {
  182. watchdog('mailgun', 'An exception occurred while retrieving domains. @code: @message', array('@code' => $e->getCode(), '@message' => $e->getMessage()), WATCHDOG_ERROR);
  183. }
  184. if (empty($domains)) {
  185. // No domain available. Although this shouldn't happen, doesn't hurt to check.
  186. return FALSE;
  187. }
  188. // Now, we need to get the working domain. This is generally the domain the From address is on or the root domain of it.
  189. $working_domain = '';
  190. if ($key = array_search($mail_domain, $domains) !== FALSE) {
  191. // Great. Found it.
  192. $working_domain = $mail_domain;
  193. }
  194. else {
  195. // Oops. No match. Perhaps it's a subdomain instead.
  196. foreach ($domains as $domain) {
  197. if (strpos($domain, $mail_domain) !== FALSE) {
  198. // Got it.
  199. $working_domain = $domain;
  200. break;
  201. }
  202. }
  203. }
  204. // There is a chance that the user is attempting to send from an email address that's on a domain not yet added to the Mailgun account.
  205. // In that case, abort sending and report error.
  206. if (empty($working_domain)) {
  207. watchdog('mailgun', 'Unable to locate a working domain for From address %mail. Aborting sending.', array('%mail' => $mailgun_message['from']), WATCHDOG_ERROR);
  208. return FALSE;
  209. }
  210. }
  211. else {
  212. $working_domain = variable_get('mailgun_domain', '');
  213. }
  214. // Attachments
  215. $post_data = array();
  216. if (!empty($mailgun_message['attachments'])) {
  217. // Send message with attachments.
  218. $post_data['attachment'] = $mailgun_message['attachments'];
  219. unset($mailgun_message['attachments']);
  220. }
  221. try {
  222. $result = $client->sendMessage($working_domain, $mailgun_message, $post_data);
  223. // For a list of HTTP response codes, see: https://documentation.mailgun.com/api-intro.html#errors.
  224. if ($result->http_response_code == 200) {
  225. if (variable_get('mailgun_log', FALSE)) {
  226. watchdog('mailgun', 'Successfully sent message from %from to %to. %code: %message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '%code' => $result->http_response_code, '%message' => $result->http_response_body->message));
  227. }
  228. return TRUE;
  229. }
  230. else {
  231. watchdog('mailgun', 'Failed to send message from %from to %to. %code: %message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '%code' => $result->http_response_code, '%message' => $result->http_response_body->message), WATCHDOG_ERROR);
  232. return FALSE;
  233. }
  234. } catch (Exception $e) {
  235. watchdog('mailgun', 'Exception occurred while trying to send test email from %from to %to. @code: @message.', array('%from' => $mailgun_message['from'], '%to' => $mailgun_message['to'], '@code' => $e->getCode(), '@message' => $e->getMessage()));
  236. }
  237. }