@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.

Update Postmark adapter for multiple mail media

Summary:
Depends on D19955. Ref T920. Ref T5969. Update Postmark to accept new Message objects. Also:

- Update the inbound whitelist.
- Add a little support for `media` configuration.
- Add a service call timeout (see T5969).
- Drop the needless word "Implementation" from the Adapter class tree. I could call these "Mailers" instead of "Adapters", but then we get "PhabricatorMailMailer" which feels questionable.

Test Plan: Used `bin/mail send-test` to send mail via Postmark with various options (mulitple recipients, text vs html, attachments).

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T5969, T920

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

+359 -295
+16 -16
src/__phutil_library_map__.php
··· 3387 3387 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', 3388 3388 'PhabricatorMacroTransactionType' => 'applications/macro/xaction/PhabricatorMacroTransactionType.php', 3389 3389 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', 3390 + 'PhabricatorMailAdapter' => 'applications/metamta/adapter/PhabricatorMailAdapter.php', 3391 + 'PhabricatorMailAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php', 3390 3392 'PhabricatorMailAttachment' => 'applications/metamta/message/PhabricatorMailAttachment.php', 3391 3393 'PhabricatorMailConfigTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMailConfigTestCase.php', 3392 3394 'PhabricatorMailEmailEngine' => 'applications/metamta/engine/PhabricatorMailEmailEngine.php', ··· 3397 3399 'PhabricatorMailEngineExtension' => 'applications/metamta/engine/PhabricatorMailEngineExtension.php', 3398 3400 'PhabricatorMailExternalMessage' => 'applications/metamta/message/PhabricatorMailExternalMessage.php', 3399 3401 'PhabricatorMailHeader' => 'applications/metamta/message/PhabricatorMailHeader.php', 3400 - 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 3401 - 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 3402 - 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', 3403 - 'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php', 3404 - 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php', 3405 - 'PhabricatorMailImplementationPostmarkAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php', 3406 - 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', 3407 - 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php', 3402 + 'PhabricatorMailMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailMailgunAdapter.php', 3408 3403 'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php', 3409 3404 'PhabricatorMailManagementListOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php', 3410 3405 'PhabricatorMailManagementReceiveTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementReceiveTestWorkflow.php', ··· 3422 3417 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php', 3423 3418 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php', 3424 3419 'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php', 3420 + 'PhabricatorMailPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailPHPMailerAdapter.php', 3421 + 'PhabricatorMailPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailPHPMailerLiteAdapter.php', 3422 + 'PhabricatorMailPostmarkAdapter' => 'applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php', 3425 3423 'PhabricatorMailPropertiesDestructionEngineExtension' => 'applications/metamta/engineextension/PhabricatorMailPropertiesDestructionEngineExtension.php', 3426 3424 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 3427 3425 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 3428 3426 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 3429 3427 'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php', 3428 + 'PhabricatorMailSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailSendGridAdapter.php', 3430 3429 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 3431 3430 'PhabricatorMailStamp' => 'applications/metamta/stamp/PhabricatorMailStamp.php', 3432 3431 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 3432 + 'PhabricatorMailTestAdapter' => 'applications/metamta/adapter/PhabricatorMailTestAdapter.php', 3433 3433 'PhabricatorMailUtil' => 'applications/metamta/util/PhabricatorMailUtil.php', 3434 3434 'PhabricatorMainMenuBarExtension' => 'view/page/menu/PhabricatorMainMenuBarExtension.php', 3435 3435 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', ··· 9221 9221 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 9222 9222 'PhabricatorMacroTransactionType' => 'PhabricatorModularTransactionType', 9223 9223 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 9224 + 'PhabricatorMailAdapter' => 'Phobject', 9225 + 'PhabricatorMailAmazonSESAdapter' => 'PhabricatorMailPHPMailerLiteAdapter', 9224 9226 'PhabricatorMailAttachment' => 'Phobject', 9225 9227 'PhabricatorMailConfigTestCase' => 'PhabricatorTestCase', 9226 9228 'PhabricatorMailEmailEngine' => 'PhabricatorMailMessageEngine', ··· 9231 9233 'PhabricatorMailEngineExtension' => 'Phobject', 9232 9234 'PhabricatorMailExternalMessage' => 'Phobject', 9233 9235 'PhabricatorMailHeader' => 'Phobject', 9234 - 'PhabricatorMailImplementationAdapter' => 'Phobject', 9235 - 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 9236 - 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', 9237 - 'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter', 9238 - 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 9239 - 'PhabricatorMailImplementationPostmarkAdapter' => 'PhabricatorMailImplementationAdapter', 9240 - 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 9241 - 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 9236 + 'PhabricatorMailMailgunAdapter' => 'PhabricatorMailAdapter', 9242 9237 'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow', 9243 9238 'PhabricatorMailManagementListOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 9244 9239 'PhabricatorMailManagementReceiveTestWorkflow' => 'PhabricatorMailManagementWorkflow', ··· 9256 9251 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 9257 9252 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 9258 9253 'PhabricatorMailOutboundStatus' => 'Phobject', 9254 + 'PhabricatorMailPHPMailerAdapter' => 'PhabricatorMailAdapter', 9255 + 'PhabricatorMailPHPMailerLiteAdapter' => 'PhabricatorMailAdapter', 9256 + 'PhabricatorMailPostmarkAdapter' => 'PhabricatorMailAdapter', 9259 9257 'PhabricatorMailPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 9260 9258 'PhabricatorMailReceiver' => 'Phobject', 9261 9259 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 9262 9260 'PhabricatorMailReplyHandler' => 'Phobject', 9263 9261 'PhabricatorMailRoutingRule' => 'Phobject', 9262 + 'PhabricatorMailSendGridAdapter' => 'PhabricatorMailAdapter', 9264 9263 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 9265 9264 'PhabricatorMailStamp' => 'Phobject', 9266 9265 'PhabricatorMailTarget' => 'Phobject', 9266 + 'PhabricatorMailTestAdapter' => 'PhabricatorMailAdapter', 9267 9267 'PhabricatorMailUtil' => 'Phobject', 9268 9268 'PhabricatorMainMenuBarExtension' => 'Phobject', 9269 9269 'PhabricatorMainMenuSearchView' => 'AphrontView',
+146
src/applications/metamta/adapter/PhabricatorMailAdapter.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorMailAdapter 4 + extends Phobject { 5 + 6 + private $key; 7 + private $priority; 8 + private $media; 9 + private $options = array(); 10 + 11 + private $supportsInbound = true; 12 + private $supportsOutbound = true; 13 + private $mediaMap; 14 + 15 + final public function getAdapterType() { 16 + return $this->getPhobjectClassConstant('ADAPTERTYPE'); 17 + } 18 + 19 + final public static function getAllAdapters() { 20 + return id(new PhutilClassMapQuery()) 21 + ->setAncestorClass(__CLASS__) 22 + ->setUniqueMethod('getAdapterType') 23 + ->execute(); 24 + } 25 + 26 + /* abstract */ public function getSupportedMessageTypes() { 27 + throw new PhutilMethodNotImplementedException(); 28 + } 29 + 30 + /* abstract */ public function sendMessage( 31 + PhabricatorMailExternalMessage $message) { 32 + throw new PhutilMethodNotImplementedException(); 33 + } 34 + 35 + /** 36 + * Return true if this adapter supports setting a "Message-ID" when sending 37 + * email. 38 + * 39 + * This is an ugly implementation detail because mail threading is a horrible 40 + * mess, implemented differently by every client in existence. 41 + */ 42 + public function supportsMessageIDHeader() { 43 + return false; 44 + } 45 + 46 + final public function supportsMessageType($message_type) { 47 + if ($this->mediaMap === null) { 48 + $media_map = $this->getSupportedMessageTypes(); 49 + $media_map = array_fuse($media_map); 50 + 51 + if ($this->media) { 52 + $config_map = $this->media; 53 + $config_map = array_fuse($config_map); 54 + 55 + $media_map = array_intersect_key($media_map, $config_map); 56 + } 57 + 58 + $this->mediaMap = $media_map; 59 + } 60 + 61 + return isset($this->mediaMap[$message_type]); 62 + } 63 + 64 + final public function setMedia(array $media) { 65 + $native_map = $this->getSupportedMessageTypes(); 66 + $native_map = array_fuse($native_map); 67 + 68 + foreach ($media as $medium) { 69 + if (!isset($native_map[$medium])) { 70 + throw new Exception( 71 + pht( 72 + 'Adapter ("%s") is configured for medium "%s", but this is not '. 73 + 'a supported delivery medium. Supported media are: %s.', 74 + $medium, 75 + implode(', ', $native_map))); 76 + } 77 + } 78 + 79 + $this->media = $media; 80 + $this->mediaMap = null; 81 + return $this; 82 + } 83 + 84 + final public function getMedia() { 85 + return $this->media; 86 + } 87 + 88 + final public function setKey($key) { 89 + $this->key = $key; 90 + return $this; 91 + } 92 + 93 + final public function getKey() { 94 + return $this->key; 95 + } 96 + 97 + final public function setPriority($priority) { 98 + $this->priority = $priority; 99 + return $this; 100 + } 101 + 102 + final public function getPriority() { 103 + return $this->priority; 104 + } 105 + 106 + final public function setSupportsInbound($supports_inbound) { 107 + $this->supportsInbound = $supports_inbound; 108 + return $this; 109 + } 110 + 111 + final public function getSupportsInbound() { 112 + return $this->supportsInbound; 113 + } 114 + 115 + final public function setSupportsOutbound($supports_outbound) { 116 + $this->supportsOutbound = $supports_outbound; 117 + return $this; 118 + } 119 + 120 + final public function getSupportsOutbound() { 121 + return $this->supportsOutbound; 122 + } 123 + 124 + final public function getOption($key) { 125 + if (!array_key_exists($key, $this->options)) { 126 + throw new Exception( 127 + pht( 128 + 'Mailer ("%s") is attempting to access unknown option ("%s").', 129 + get_class($this), 130 + $key)); 131 + } 132 + 133 + return $this->options[$key]; 134 + } 135 + 136 + final public function setOptions(array $options) { 137 + $this->validateOptions($options); 138 + $this->options = $options; 139 + return $this; 140 + } 141 + 142 + abstract protected function validateOptions(array $options); 143 + 144 + abstract public function newDefaultOptions(); 145 + 146 + }
-127
src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
··· 1 - <?php 2 - 3 - abstract class PhabricatorMailImplementationAdapter extends Phobject { 4 - 5 - private $key; 6 - private $priority; 7 - private $options = array(); 8 - 9 - private $supportsInbound = true; 10 - private $supportsOutbound = true; 11 - 12 - final public function getAdapterType() { 13 - return $this->getPhobjectClassConstant('ADAPTERTYPE'); 14 - } 15 - 16 - final public static function getAllAdapters() { 17 - return id(new PhutilClassMapQuery()) 18 - ->setAncestorClass(__CLASS__) 19 - ->setUniqueMethod('getAdapterType') 20 - ->execute(); 21 - } 22 - 23 - 24 - abstract public function setFrom($email, $name = ''); 25 - abstract public function addReplyTo($email, $name = ''); 26 - abstract public function addTos(array $emails); 27 - abstract public function addCCs(array $emails); 28 - abstract public function addAttachment($data, $filename, $mimetype); 29 - abstract public function addHeader($header_name, $header_value); 30 - abstract public function setBody($plaintext_body); 31 - abstract public function setHTMLBody($html_body); 32 - abstract public function setSubject($subject); 33 - 34 - 35 - /** 36 - * Some mailers, notably Amazon SES, do not support us setting a specific 37 - * Message-ID header. 38 - */ 39 - abstract public function supportsMessageIDHeader(); 40 - 41 - 42 - /** 43 - * Send the message. Generally, this means connecting to some service and 44 - * handing data to it. 45 - * 46 - * If the adapter determines that the mail will never be deliverable, it 47 - * should throw a @{class:PhabricatorMetaMTAPermanentFailureException}. 48 - * 49 - * For temporary failures, throw some other exception or return `false`. 50 - * 51 - * @return bool True on success. 52 - */ 53 - abstract public function send(); 54 - 55 - final public function setKey($key) { 56 - $this->key = $key; 57 - return $this; 58 - } 59 - 60 - final public function getKey() { 61 - return $this->key; 62 - } 63 - 64 - final public function setPriority($priority) { 65 - $this->priority = $priority; 66 - return $this; 67 - } 68 - 69 - final public function getPriority() { 70 - return $this->priority; 71 - } 72 - 73 - final public function setSupportsInbound($supports_inbound) { 74 - $this->supportsInbound = $supports_inbound; 75 - return $this; 76 - } 77 - 78 - final public function getSupportsInbound() { 79 - return $this->supportsInbound; 80 - } 81 - 82 - final public function setSupportsOutbound($supports_outbound) { 83 - $this->supportsOutbound = $supports_outbound; 84 - return $this; 85 - } 86 - 87 - final public function getSupportsOutbound() { 88 - return $this->supportsOutbound; 89 - } 90 - 91 - final public function getOption($key) { 92 - if (!array_key_exists($key, $this->options)) { 93 - throw new Exception( 94 - pht( 95 - 'Mailer ("%s") is attempting to access unknown option ("%s").', 96 - get_class($this), 97 - $key)); 98 - } 99 - 100 - return $this->options[$key]; 101 - } 102 - 103 - final public function setOptions(array $options) { 104 - $this->validateOptions($options); 105 - $this->options = $options; 106 - return $this; 107 - } 108 - 109 - abstract protected function validateOptions(array $options); 110 - 111 - abstract public function newDefaultOptions(); 112 - 113 - public function prepareForSend() { 114 - return; 115 - } 116 - 117 - protected function renderAddress($email, $name = null) { 118 - if (strlen($name)) { 119 - return (string)id(new PhutilEmailAddress()) 120 - ->setDisplayName($name) 121 - ->setAddress($email); 122 - } else { 123 - return $email; 124 - } 125 - } 126 - 127 - }
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php src/applications/metamta/adapter/PhabricatorMailAmazonSESAdapter.php
··· 1 1 <?php 2 2 3 - final class PhabricatorMailImplementationAmazonSESAdapter 4 - extends PhabricatorMailImplementationPHPMailerLiteAdapter { 3 + final class PhabricatorMailAmazonSESAdapter 4 + extends PhabricatorMailPHPMailerLiteAdapter { 5 5 6 6 const ADAPTERTYPE = 'ses'; 7 7
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php src/applications/metamta/adapter/PhabricatorMailMailgunAdapter.php
··· 3 3 /** 4 4 * Mail adapter that uses Mailgun's web API to deliver email. 5 5 */ 6 - final class PhabricatorMailImplementationMailgunAdapter 7 - extends PhabricatorMailImplementationAdapter { 6 + final class PhabricatorMailMailgunAdapter 7 + extends PhabricatorMailAdapter { 8 8 9 9 const ADAPTERTYPE = 'mailgun'; 10 10
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php src/applications/metamta/adapter/PhabricatorMailPHPMailerAdapter.php
··· 1 1 <?php 2 2 3 - final class PhabricatorMailImplementationPHPMailerAdapter 4 - extends PhabricatorMailImplementationAdapter { 3 + final class PhabricatorMailPHPMailerAdapter 4 + extends PhabricatorMailAdapter { 5 5 6 6 const ADAPTERTYPE = 'smtp'; 7 7
+4 -2
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php src/applications/metamta/adapter/PhabricatorMailPHPMailerLiteAdapter.php
··· 2 2 3 3 /** 4 4 * TODO: Should be final, but inherited by SES. 5 + * 6 + * @concrete-extensible 5 7 */ 6 - class PhabricatorMailImplementationPHPMailerLiteAdapter 7 - extends PhabricatorMailImplementationAdapter { 8 + class PhabricatorMailPHPMailerLiteAdapter 9 + extends PhabricatorMailAdapter { 8 10 9 11 const ADAPTERTYPE = 'sendmail'; 10 12
-120
src/applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php
··· 1 - <?php 2 - 3 - final class PhabricatorMailImplementationPostmarkAdapter 4 - extends PhabricatorMailImplementationAdapter { 5 - 6 - const ADAPTERTYPE = 'postmark'; 7 - 8 - private $parameters = array(); 9 - 10 - public function setFrom($email, $name = '') { 11 - $this->parameters['From'] = $this->renderAddress($email, $name); 12 - return $this; 13 - } 14 - 15 - public function addReplyTo($email, $name = '') { 16 - $this->parameters['ReplyTo'] = $this->renderAddress($email, $name); 17 - return $this; 18 - } 19 - 20 - public function addTos(array $emails) { 21 - foreach ($emails as $email) { 22 - $this->parameters['To'][] = $email; 23 - } 24 - return $this; 25 - } 26 - 27 - public function addCCs(array $emails) { 28 - foreach ($emails as $email) { 29 - $this->parameters['Cc'][] = $email; 30 - } 31 - return $this; 32 - } 33 - 34 - public function addAttachment($data, $filename, $mimetype) { 35 - $this->parameters['Attachments'][] = array( 36 - 'Name' => $filename, 37 - 'ContentType' => $mimetype, 38 - 'Content' => base64_encode($data), 39 - ); 40 - 41 - return $this; 42 - } 43 - 44 - public function addHeader($header_name, $header_value) { 45 - $this->parameters['Headers'][] = array( 46 - 'Name' => $header_name, 47 - 'Value' => $header_value, 48 - ); 49 - return $this; 50 - } 51 - 52 - public function setBody($body) { 53 - $this->parameters['TextBody'] = $body; 54 - return $this; 55 - } 56 - 57 - public function setHTMLBody($html_body) { 58 - $this->parameters['HtmlBody'] = $html_body; 59 - return $this; 60 - } 61 - 62 - public function setSubject($subject) { 63 - $this->parameters['Subject'] = $subject; 64 - return $this; 65 - } 66 - 67 - public function supportsMessageIDHeader() { 68 - return true; 69 - } 70 - 71 - protected function validateOptions(array $options) { 72 - PhutilTypeSpec::checkMap( 73 - $options, 74 - array( 75 - 'access-token' => 'string', 76 - 'inbound-addresses' => 'list<string>', 77 - )); 78 - 79 - // Make sure this is properly formatted. 80 - PhutilCIDRList::newList($options['inbound-addresses']); 81 - } 82 - 83 - public function newDefaultOptions() { 84 - return array( 85 - 'access-token' => null, 86 - 'inbound-addresses' => array( 87 - // Via Postmark support circa February 2018, see: 88 - // 89 - // https://postmarkapp.com/support/article/800-ips-for-firewalls 90 - // 91 - // "Configuring Outbound Email" should be updated if this changes. 92 - '50.31.156.6/32', 93 - ), 94 - ); 95 - } 96 - 97 - public function send() { 98 - $access_token = $this->getOption('access-token'); 99 - 100 - $parameters = $this->parameters; 101 - $flatten = array( 102 - 'To', 103 - 'Cc', 104 - ); 105 - 106 - foreach ($flatten as $key) { 107 - if (isset($parameters[$key])) { 108 - $parameters[$key] = implode(', ', $parameters[$key]); 109 - } 110 - } 111 - 112 - id(new PhutilPostmarkFuture()) 113 - ->setAccessToken($access_token) 114 - ->setMethod('email', $parameters) 115 - ->resolve(); 116 - 117 - return true; 118 - } 119 - 120 - }
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php
··· 3 3 /** 4 4 * Mail adapter that uses SendGrid's web API to deliver email. 5 5 */ 6 - final class PhabricatorMailImplementationSendGridAdapter 7 - extends PhabricatorMailImplementationAdapter { 6 + final class PhabricatorMailSendGridAdapter 7 + extends PhabricatorMailAdapter { 8 8 9 9 const ADAPTERTYPE = 'sendgrid'; 10 10
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php src/applications/metamta/adapter/PhabricatorMailTestAdapter.php
··· 4 4 * Mail adapter that doesn't actually send any email, for writing unit tests 5 5 * against. 6 6 */ 7 - final class PhabricatorMailImplementationTestAdapter 8 - extends PhabricatorMailImplementationAdapter { 7 + final class PhabricatorMailTestAdapter 8 + extends PhabricatorMailAdapter { 9 9 10 10 const ADAPTERTYPE = 'test'; 11 11
+128
src/applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php
··· 1 + <?php 2 + 3 + final class PhabricatorMailPostmarkAdapter 4 + extends PhabricatorMailAdapter { 5 + 6 + const ADAPTERTYPE = 'postmark'; 7 + 8 + public function getSupportedMessageTypes() { 9 + return array( 10 + PhabricatorMailEmailMessage::MESSAGETYPE, 11 + ); 12 + } 13 + 14 + public function supportsMessageIDHeader() { 15 + return true; 16 + } 17 + 18 + protected function validateOptions(array $options) { 19 + PhutilTypeSpec::checkMap( 20 + $options, 21 + array( 22 + 'access-token' => 'string', 23 + 'inbound-addresses' => 'list<string>', 24 + )); 25 + 26 + // Make sure this is properly formatted. 27 + PhutilCIDRList::newList($options['inbound-addresses']); 28 + } 29 + 30 + public function newDefaultOptions() { 31 + return array( 32 + 'access-token' => null, 33 + 'inbound-addresses' => array( 34 + // Via Postmark support circa February 2018, see: 35 + // 36 + // https://postmarkapp.com/support/article/800-ips-for-firewalls 37 + // 38 + // "Configuring Outbound Email" should be updated if this changes. 39 + // 40 + // These addresses were last updated in January 2019. 41 + '50.31.156.6/32', 42 + '50.31.156.77/32', 43 + '18.217.206.57/32', 44 + ), 45 + ); 46 + } 47 + 48 + public function sendMessage(PhabricatorMailExternalMessage $message) { 49 + $access_token = $this->getOption('access-token'); 50 + 51 + $parameters = array(); 52 + 53 + $subject = $message->getSubject(); 54 + if ($subject !== null) { 55 + $parameters['Subject'] = $subject; 56 + } 57 + 58 + $from_address = $message->getFromAddress(); 59 + if ($from_address) { 60 + $parameters['From'] = (string)$from_address; 61 + } 62 + 63 + $to_addresses = $message->getToAddresses(); 64 + if ($to_addresses) { 65 + $to = array(); 66 + foreach ($to_addresses as $address) { 67 + $to[] = (string)$address; 68 + } 69 + $parameters['To'] = implode(', ', $to); 70 + } 71 + 72 + $cc_addresses = $message->getCCAddresses(); 73 + if ($cc_addresses) { 74 + $cc = array(); 75 + foreach ($cc_addresses as $address) { 76 + $cc[] = (string)$address; 77 + } 78 + $parameters['Cc'] = implode(', ', $cc); 79 + } 80 + 81 + $reply_address = $message->getReplyToAddress(); 82 + if ($reply_address) { 83 + $parameters['ReplyTo'] = (string)$reply_address; 84 + } 85 + 86 + $headers = $message->getHeaders(); 87 + if ($headers) { 88 + $list = array(); 89 + foreach ($headers as $header) { 90 + $list[] = array( 91 + 'Name' => $header->getName(), 92 + 'Value' => $header->getValue(), 93 + ); 94 + } 95 + $parameters['Headers'] = $list; 96 + } 97 + 98 + $text_body = $message->getTextBody(); 99 + if ($text_body !== null) { 100 + $parameters['TextBody'] = $text_body; 101 + } 102 + 103 + $html_body = $message->getHTMLBody(); 104 + if ($html_body !== null) { 105 + $parameters['HtmlBody'] = $html_body; 106 + } 107 + 108 + $attachments = $message->getAttachments(); 109 + if ($attachments) { 110 + $files = array(); 111 + foreach ($attachments as $attachment) { 112 + $files[] = array( 113 + 'Name' => $attachment->getFilename(), 114 + 'ContentType' => $attachment->getMimeType(), 115 + 'Content' => base64_encode($attachment->getData()), 116 + ); 117 + } 118 + $parameters['Attachments'] = $files; 119 + } 120 + 121 + id(new PhutilPostmarkFuture()) 122 + ->setAccessToken($access_token) 123 + ->setMethod('email', $parameters) 124 + ->setTimeout(60) 125 + ->resolve(); 126 + } 127 + 128 + }
+1 -1
src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
··· 21 21 array( 22 22 'inbound' => true, 23 23 'types' => array( 24 - PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE, 24 + PhabricatorMailMailgunAdapter::ADAPTERTYPE, 25 25 ), 26 26 )); 27 27 foreach ($mailers as $mailer) {
+1 -1
src/applications/metamta/controller/PhabricatorMetaMTAPostmarkReceiveController.php
··· 16 16 array( 17 17 'inbound' => true, 18 18 'types' => array( 19 - PhabricatorMailImplementationPostmarkAdapter::ADAPTERTYPE, 19 + PhabricatorMailPostmarkAdapter::ADAPTERTYPE, 20 20 ), 21 21 )); 22 22 if (!$mailers) {
+1 -1
src/applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php
··· 15 15 array( 16 16 'inbound' => true, 17 17 'types' => array( 18 - PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE, 18 + PhabricatorMailSendGridAdapter::ADAPTERTYPE, 19 19 ), 20 20 )); 21 21 if (!$mailers) {
+1 -2
src/applications/metamta/engine/PhabricatorMailMessageEngine.php
··· 8 8 private $actors = array(); 9 9 private $preferences; 10 10 11 - final public function setMailer( 12 - PhabricatorMailImplementationAdapter $mailer) { 11 + final public function setMailer(PhabricatorMailAdapter $mailer) { 13 12 14 13 $this->mailer = $mailer; 15 14 return $this;
+25 -1
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 547 547 'types' => 'optional list<string>', 548 548 'inbound' => 'optional bool', 549 549 'outbound' => 'optional bool', 550 + 'media' => 'optional list<string>', 550 551 )); 551 552 552 553 $mailers = array(); 553 554 554 555 $config = PhabricatorEnv::getEnvConfig('cluster.mailers'); 555 556 556 - $adapters = PhabricatorMailImplementationAdapter::getAllAdapters(); 557 + $adapters = PhabricatorMailAdapter::getAllAdapters(); 557 558 $next_priority = -1; 558 559 559 560 foreach ($config as $spec) { ··· 583 584 $mailer->setSupportsInbound(idx($spec, 'inbound', true)); 584 585 $mailer->setSupportsOutbound(idx($spec, 'outbound', true)); 585 586 587 + $media = idx($spec, 'media'); 588 + if ($media !== null) { 589 + $mailer->setMedia($media); 590 + } 591 + 586 592 $mailers[] = $mailer; 587 593 } 588 594 ··· 613 619 if (!empty($constraints['outbound'])) { 614 620 foreach ($mailers as $key => $mailer) { 615 621 if (!$mailer->getSupportsOutbound()) { 622 + unset($mailers[$key]); 623 + } 624 + } 625 + } 626 + 627 + // Select only the mailers which can transmit messages with requested media 628 + // types. 629 + if (!empty($constraints['media'])) { 630 + foreach ($mailers as $key => $mailer) { 631 + $supports_any = false; 632 + foreach ($constraints['media'] as $medium) { 633 + if ($mailer->supportsMessageType($medium)) { 634 + $supports_any = true; 635 + break; 636 + } 637 + } 638 + 639 + if (!$supports_any) { 616 640 unset($mailers[$key]); 617 641 } 618 642 }
+10 -10
src/applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php
··· 17 17 $mail = new PhabricatorMetaMTAMail(); 18 18 $mail->addTos(array($phid)); 19 19 20 - $mailer = new PhabricatorMailImplementationTestAdapter(); 20 + $mailer = new PhabricatorMailTestAdapter(); 21 21 $mail->sendWithMailers(array($mailer)); 22 22 $this->assertEqual( 23 23 PhabricatorMailOutboundStatus::STATUS_SENT, ··· 28 28 $mail = new PhabricatorMetaMTAMail(); 29 29 $mail->addTos(array($phid)); 30 30 31 - $mailer = new PhabricatorMailImplementationTestAdapter(); 31 + $mailer = new PhabricatorMailTestAdapter(); 32 32 $mailer->setFailTemporarily(true); 33 33 try { 34 34 $mail->sendWithMailers(array($mailer)); ··· 44 44 $mail = new PhabricatorMetaMTAMail(); 45 45 $mail->addTos(array($phid)); 46 46 47 - $mailer = new PhabricatorMailImplementationTestAdapter(); 47 + $mailer = new PhabricatorMailTestAdapter(); 48 48 $mailer->setFailPermanently(true); 49 49 try { 50 50 $mail->sendWithMailers(array($mailer)); ··· 60 60 $user = $this->generateNewTestUser(); 61 61 $phid = $user->getPHID(); 62 62 63 - $mailer = new PhabricatorMailImplementationTestAdapter(); 63 + $mailer = new PhabricatorMailTestAdapter(); 64 64 65 65 $mail = new PhabricatorMetaMTAMail(); 66 66 $mail->addTos(array($phid)); ··· 182 182 $supports_message_id, 183 183 $is_first_mail) { 184 184 185 - $mailer = new PhabricatorMailImplementationTestAdapter(); 185 + $mailer = new PhabricatorMailTestAdapter(); 186 186 187 187 $mailer->prepareForSend( 188 188 array( ··· 261 261 $status_queue = PhabricatorMailOutboundStatus::STATUS_QUEUE; 262 262 $status_fail = PhabricatorMailOutboundStatus::STATUS_FAIL; 263 263 264 - $mailer1 = id(new PhabricatorMailImplementationTestAdapter()) 264 + $mailer1 = id(new PhabricatorMailTestAdapter()) 265 265 ->setKey('mailer1'); 266 266 267 - $mailer2 = id(new PhabricatorMailImplementationTestAdapter()) 267 + $mailer2 = id(new PhabricatorMailTestAdapter()) 268 268 ->setKey('mailer2'); 269 269 270 270 $mailers = array( ··· 350 350 ->setBody($string_1kb) 351 351 ->setHTMLBody($html_1kb); 352 352 353 - $mailer = new PhabricatorMailImplementationTestAdapter(); 353 + $mailer = new PhabricatorMailTestAdapter(); 354 354 $mail->sendWithMailers(array($mailer)); 355 355 $this->assertEqual( 356 356 PhabricatorMailOutboundStatus::STATUS_SENT, ··· 370 370 ->setBody($string_1mb) 371 371 ->setHTMLBody($html_1mb); 372 372 373 - $mailer = new PhabricatorMailImplementationTestAdapter(); 373 + $mailer = new PhabricatorMailTestAdapter(); 374 374 $mail->sendWithMailers(array($mailer)); 375 375 $this->assertEqual( 376 376 PhabricatorMailOutboundStatus::STATUS_SENT, ··· 398 398 ->setBody($string_1kb) 399 399 ->setHTMLBody($html_1mb); 400 400 401 - $mailer = new PhabricatorMailImplementationTestAdapter(); 401 + $mailer = new PhabricatorMailTestAdapter(); 402 402 $mail->sendWithMailers(array($mailer)); 403 403 $this->assertEqual( 404 404 PhabricatorMailOutboundStatus::STATUS_SENT,
+15 -3
src/docs/user/configuration/configuring_outbound_email.diviner
··· 85 85 used to receive inbound mail. 86 86 - `outbound`: Optional bool. Use `false` to prevent this mailer from being 87 87 used to send outbound mail. 88 + - `media`: Optional list<string>. Some mailers support delivering multiple 89 + types of messages (like Email and SMS). If you want to configure a mailer 90 + to support only a subset of possible message types, list only those message 91 + types. Normally, you do not need to configure this. See below for a list 92 + of media types. 88 93 89 94 The `type` field can be used to select these third-party mailers: 90 95 91 96 - `mailgun`: Use Mailgun. 92 97 - `ses`: Use Amazon SES. 93 98 - `sendgrid`: Use Sendgrid. 99 + - `postmark`: Use Postmark. 94 100 95 101 It also supports these local mailers: 96 102 ··· 99 105 - `test`: Internal mailer for testing. Does not send mail. 100 106 101 107 You can also write your own mailer by extending 102 - `PhabricatorMailImplementationAdapter`. 108 + `PhabricatorMailAdapter`. 109 + 110 + The `media` field supports these values: 111 + 112 + - `email`: Configure this mailer for email. 103 113 104 114 Once you've selected a mailer, find the corresponding section below for 105 115 instructions on configuring it. ··· 171 181 172 182 ```lang=json 173 183 [ 174 - "50.31.156.6/32" 184 + "50.31.156.6/32", 185 + "50.31.156.77/32", 186 + "18.217.206.57/32" 175 187 ] 176 188 ``` 177 189 178 - The default address ranges were last updated in February 2018, and were 190 + The default address ranges were last updated in January 2019, and were 179 191 documented at: <https://postmarkapp.com/support/article/800-ips-for-firewalls> 180 192 181 193
+1 -1
src/infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php
··· 31 31 } 32 32 } 33 33 34 - $adapters = PhabricatorMailImplementationAdapter::getAllAdapters(); 34 + $adapters = PhabricatorMailAdapter::getAllAdapters(); 35 35 36 36 $map = array(); 37 37 foreach ($value as $index => $spec) {