@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Mailgun receive support

Summary:
As you've suggested, I took the SendGrid code and massaged it until it played nice with Mailgun.

btw - unless I'm missing something, it appears that the SendGrid receiver lets you spoof emails (it performs no validation on the data received).

Test Plan: Opened a task with Mailgun. Felt great.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley

CC: Korvin, epriestley, aran

Maniphest Tasks: T4326

Differential Revision: https://secure.phabricator.com/D7989

authored by

Tal Shiri and committed by
epriestley
a9612fac dc74da0a

+233
+6
src/__phutil_library_map__.php
··· 1575 1575 'PhabricatorMail' => 'applications/metamta/PhabricatorMail.php', 1576 1576 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 1577 1577 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 1578 + 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', 1578 1579 'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php', 1579 1580 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php', 1580 1581 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', ··· 1590 1591 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 1591 1592 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 1592 1593 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 1594 + 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', 1593 1595 'PhabricatorMailingListPHIDTypeList' => 'applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php', 1594 1596 'PhabricatorMailingListQuery' => 'applications/mailinglists/query/PhabricatorMailingListQuery.php', 1595 1597 'PhabricatorMailingListSearchEngine' => 'applications/mailinglists/query/PhabricatorMailingListSearchEngine.php', ··· 1621 1623 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', 1622 1624 'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php', 1623 1625 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 1626 + 'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php', 1624 1627 'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php', 1625 1628 'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php', 1626 1629 'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php', ··· 4211 4214 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4212 4215 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 4213 4216 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 4217 + 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', 4214 4218 'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter', 4215 4219 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 4216 4220 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', ··· 4224 4228 'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 4225 4229 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', 4226 4230 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 4231 + 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', 4227 4232 'PhabricatorMailingListPHIDTypeList' => 'PhabricatorPHIDType', 4228 4233 'PhabricatorMailingListQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4229 4234 'PhabricatorMailingListSearchEngine' => 'PhabricatorApplicationSearchEngine', ··· 4253 4258 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 4254 4259 'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase', 4255 4260 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 4261 + 'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController', 4256 4262 'PhabricatorMetaMTAMailingList' => 4257 4263 array( 4258 4264 0 => 'PhabricatorMetaMTADAO',
+28
src/applications/config/option/PhabricatorMailgunConfigOptions.php
··· 1 + <?php 2 + 3 + final class PhabricatorMailgunConfigOptions 4 + extends PhabricatorApplicationConfigOptions { 5 + 6 + public function getName() { 7 + return pht("Integration with Mailgun"); 8 + } 9 + 10 + public function getDescription() { 11 + return pht("Configure Mailgun integration."); 12 + } 13 + 14 + public function getOptions() { 15 + return array( 16 + $this->newOption('mailgun.api-key', 'string', null) 17 + ->setMasked(true) 18 + ->setDescription(pht('Mailgun API key.')), 19 + $this->newOption('mailgun.domain', 'string', null) 20 + ->setDescription( 21 + pht( 22 + 'Mailgun domain name. See https://mailgun.com/cp/domains')) 23 + ->addExample('mycompany.com', 'Use specific domain'), 24 + ); 25 + 26 + } 27 + 28 + }
+122
src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php
··· 1 + <?php 2 + 3 + /** 4 + * Mail adapter that uses Mailgun's web API to deliver email. 5 + */ 6 + final class PhabricatorMailImplementationMailgunAdapter 7 + extends PhabricatorMailImplementationAdapter { 8 + 9 + private $params = array(); 10 + 11 + public function setFrom($email, $name = '') { 12 + $this->params['from'] = $email; 13 + $this->params['from-name'] = $name; 14 + return $this; 15 + } 16 + 17 + public function addReplyTo($email, $name = '') { 18 + if (empty($this->params['reply-to'])) { 19 + $this->params['reply-to'] = array(); 20 + } 21 + $this->params['reply-to'][] = array( 22 + 'email' => $email, 23 + 'name' => $name, 24 + ); 25 + return $this; 26 + } 27 + 28 + public function addTos(array $emails) { 29 + foreach ($emails as $email) { 30 + $this->params['tos'][] = $email; 31 + } 32 + return $this; 33 + } 34 + 35 + public function addCCs(array $emails) { 36 + foreach ($emails as $email) { 37 + $this->params['ccs'][] = $email; 38 + } 39 + return $this; 40 + } 41 + 42 + public function addAttachment($data, $filename, $mimetype) { 43 + // TODO: implement attachments. Requires changes in HTTPSFuture 44 + throw new Exception( 45 + "Mailgun adapter does not currently support attachments."); 46 + } 47 + 48 + public function addHeader($header_name, $header_value) { 49 + $this->params['headers'][] = array($header_name, $header_value); 50 + return $this; 51 + } 52 + 53 + public function setBody($body) { 54 + $this->params['body'] = $body; 55 + return $this; 56 + } 57 + 58 + public function setSubject($subject) { 59 + $this->params['subject'] = $subject; 60 + return $this; 61 + } 62 + 63 + public function setIsHTML($is_html) { 64 + $this->params['is-html'] = $is_html; 65 + return $this; 66 + } 67 + 68 + public function supportsMessageIDHeader() { 69 + return false; 70 + } 71 + 72 + public function send() { 73 + $key = PhabricatorEnv::getEnvConfig('mailgun.api-key'); 74 + $domain = PhabricatorEnv::getEnvConfig('mailgun.domain'); 75 + $params = array(); 76 + 77 + $params['to'] = idx($this->params, 'tos', array()); 78 + $params['subject'] = idx($this->params, 'subject'); 79 + 80 + if (idx($this->params, 'is-html')) { 81 + $params['html'] = idx($this->params, 'body'); 82 + } else { 83 + $params['text'] = idx($this->params, 'body'); 84 + } 85 + 86 + $from = idx($this->params, 'from'); 87 + if (idx($this->params, 'from-name')) { 88 + $params['from'] = "{$this->params['from-name']} <{$from}>"; 89 + } else { 90 + $params['from'] = $from; 91 + } 92 + 93 + if (idx($this->params, 'reply-to')) { 94 + $replyto = $this->params['reply-to']; 95 + $params['h:reply-to'] = $replyto; 96 + } 97 + 98 + if (idx($this->params, 'ccs')) { 99 + $params['cc'] = $this->params['ccs']; 100 + } 101 + 102 + $future = new HTTPSFuture( 103 + "https://api:{$key}@api.mailgun.net/v2/{$domain}/messages", 104 + $params); 105 + $future->setMethod('POST'); 106 + 107 + list($body) = $future->resolvex(); 108 + 109 + $response = json_decode($body, true); 110 + if (!is_array($response)) { 111 + throw new Exception("Failed to JSON decode response: {$body}"); 112 + } 113 + 114 + if (!idx($response, 'id')) { 115 + $message = $response['message']; 116 + throw new Exception("Request failed with errors: {$message}."); 117 + } 118 + 119 + return true; 120 + } 121 + 122 + }
+1
src/applications/metamta/application/PhabricatorApplicationMetaMTA.php
··· 34 34 return array( 35 35 $this->getBaseURI() => array( 36 36 'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController', 37 + 'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController', 37 38 ), 38 39 ); 39 40 }
+76
src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAMailgunReceiveController 4 + extends PhabricatorMetaMTAController { 5 + 6 + public function shouldRequireLogin() { 7 + return false; 8 + } 9 + 10 + private function verifyMessage() { 11 + $api_key = PhabricatorEnv::getEnvConfig('mailgun.api-key'); 12 + $request = $this->getRequest(); 13 + $timestamp = $request->getStr('timestamp'); 14 + $token = $request->getStr('token'); 15 + $sig = $request->getStr('signature'); 16 + return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig; 17 + 18 + } 19 + public function processRequest() { 20 + 21 + // No CSRF for Mailgun. 22 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 23 + 24 + if (!$this->verifyMessage()) { 25 + throw new Exception( 26 + 'Mail signature is not valid. Check your Mailgun API key.'); 27 + } 28 + 29 + $request = $this->getRequest(); 30 + $user = $request->getUser(); 31 + 32 + $raw_headers = $request->getStr('headers'); 33 + $raw_headers = explode("\n", rtrim($raw_headers)); 34 + $raw_dict = array(); 35 + foreach (array_filter($raw_headers) as $header) { 36 + list($name, $value) = explode(':', $header, 2); 37 + $raw_dict[$name] = ltrim($value); 38 + } 39 + 40 + $headers = array( 41 + 'to' => $request->getStr('recipient'), 42 + 'from' => $request->getStr('from'), 43 + 'subject' => $request->getStr('subject'), 44 + ) + $raw_dict; 45 + 46 + $received = new PhabricatorMetaMTAReceivedMail(); 47 + $received->setHeaders($headers); 48 + $received->setBodies(array( 49 + 'text' => $request->getStr('stripped-text'), 50 + 'html' => $request->getStr('stripped-html'), 51 + )); 52 + 53 + $file_phids = array(); 54 + foreach ($_FILES as $file_raw) { 55 + try { 56 + $file = PhabricatorFile::newFromPHPUpload( 57 + $file_raw, 58 + array( 59 + 'authorPHID' => $user->getPHID(), 60 + )); 61 + $file_phids[] = $file->getPHID(); 62 + } catch (Exception $ex) { 63 + phlog($ex); 64 + } 65 + } 66 + $received->setAttachments($file_phids); 67 + $received->save(); 68 + 69 + $received->processReceivedMail(); 70 + 71 + $response = new AphrontWebpageResponse(); 72 + $response->setContent(pht("Got it! Thanks, Mailgun!\n")); 73 + return $response; 74 + } 75 + 76 + }