From 19b3fb8863d6b72dbb08d904069dc9a914f95c69 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Feb 2018 09:29:40 -0800 Subject: [PATCH] Add a Postmark mail adapter so it can be configured as an outbound mailer Summary: Depends on D19007. Ref T12677. Test Plan: Used `bin/mail send-test ... --mailer postmark` to deliver some mail via Postmark. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T12677 Differential Revision: https://secure.phabricator.com/D19009 --- src/__phutil_library_map__.php | 2 + .../PhabricatorMailImplementationAdapter.php | 9 ++ ...catorMailImplementationPostmarkAdapter.php | 112 ++++++++++++++++++ ...bricatorMailManagementSendTestWorkflow.php | 20 ++++ .../storage/PhabricatorMetaMTAMail.php | 10 ++ .../configuring_outbound_email.diviner | 12 ++ 6 files changed, 165 insertions(+) create mode 100644 src/applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 29a97c43d2..16b3e1257a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3188,6 +3188,7 @@ phutil_register_library_map(array( 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', 'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php', + 'PhabricatorMailImplementationPostmarkAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php', 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php', 'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php', @@ -8691,6 +8692,7 @@ phutil_register_library_map(array( 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', + 'PhabricatorMailImplementationPostmarkAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow', diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php index 14c7a63663..ce56345194 100644 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php @@ -94,4 +94,13 @@ abstract class PhabricatorMailImplementationAdapter extends Phobject { return; } + protected function renderAddress($email, $name = null) { + if (strlen($name)) { + // TODO: This needs to be escaped correctly. + return "{$name} <{$email}>"; + } else { + return $email; + } + } + } diff --git a/src/applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php b/src/applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php new file mode 100644 index 0000000000..bd5ee820af --- /dev/null +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationPostmarkAdapter.php @@ -0,0 +1,112 @@ +parameters['From'] = $this->renderAddress($email, $name); + return $this; + } + + public function addReplyTo($email, $name = '') { + $this->parameters['ReplyTo'] = $this->renderAddress($email, $name); + return $this; + } + + public function addTos(array $emails) { + foreach ($emails as $email) { + $this->parameters['To'][] = $email; + } + return $this; + } + + public function addCCs(array $emails) { + foreach ($emails as $email) { + $this->parameters['Cc'][] = $email; + } + return $this; + } + + public function addAttachment($data, $filename, $mimetype) { + $this->parameters['Attachments'][] = array( + 'Name' => $filename, + 'ContentType' => $mimetype, + 'Content' => base64_encode($data), + ); + + return $this; + } + + public function addHeader($header_name, $header_value) { + $this->parameters['Headers'][] = array( + 'Name' => $header_name, + 'Value' => $header_value, + ); + return $this; + } + + public function setBody($body) { + $this->parameters['TextBody'] = $body; + return $this; + } + + public function setHTMLBody($html_body) { + $this->parameters['HtmlBody'] = $html_body; + return $this; + } + + public function setSubject($subject) { + $this->parameters['Subject'] = $subject; + return $this; + } + + public function supportsMessageIDHeader() { + return true; + } + + protected function validateOptions(array $options) { + PhutilTypeSpec::checkMap( + $options, + array( + 'access-token' => 'string', + )); + } + + public function newDefaultOptions() { + return array( + 'access-token' => null, + ); + } + + public function newLegacyOptions() { + return array(); + } + + public function send() { + $access_token = $this->getOption('access-token'); + + $parameters = $this->parameters; + $flatten = array( + 'To', + 'Cc', + ); + + foreach ($flatten as $key) { + if (isset($parameters[$key])) { + $parameters[$key] = implode(', ', $parameters[$key]); + } + } + + id(new PhutilPostmarkFuture()) + ->setAccessToken($access_token) + ->setMethod('email', $parameters) + ->resolve(); + + return true; + } + +} diff --git a/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php index 9f4e91ca22..152b62fd3f 100644 --- a/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php +++ b/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php @@ -47,6 +47,11 @@ final class PhabricatorMailManagementSendTestWorkflow 'help' => pht('Attach a file.'), 'repeat' => true, ), + array( + 'name' => 'mailer', + 'param' => 'key', + 'help' => pht('Send with a specific configured mailer.'), + ), array( 'name' => 'html', 'help' => pht('Send as HTML mail.'), @@ -161,6 +166,21 @@ final class PhabricatorMailManagementSendTestWorkflow $mail->setFrom($from->getPHID()); } + $mailer_key = $args->getArg('mailer'); + if ($mailer_key !== null) { + $mailers = PhabricatorMetaMTAMail::newMailers(); + $mailers = mpull($mailers, null, 'getKey'); + if (!isset($mailers[$mailer_key])) { + throw new PhutilArgumentUsageException( + pht( + 'Mailer key ("%s") is not configured. Available keys are: %s.', + $mailer_key, + implode(', ', array_keys($mailers)))); + } + + $mail->setTryMailers(array($mailer_key)); + } + foreach ($attach as $attachment) { $data = Filesystem::readFile($attachment); $name = basename($attachment); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index 5f859fd1d9..83cdc7c40f 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -319,6 +319,10 @@ final class PhabricatorMetaMTAMail return $this->getParam('mailer.key'); } + public function setTryMailers(array $mailers) { + return $this->setParam('mailers.try', $mailers); + } + public function setHTMLBody($html) { $this->setParam('html-body', $html); return $this; @@ -469,6 +473,12 @@ final class PhabricatorMetaMTAMail $mailers = self::newMailers(); + $try_mailers = $this->getParam('mailers.try'); + if ($try_mailers) { + $mailers = mpull($mailers, null, 'getKey'); + $mailers = array_select_keys($mailers, $try_mailers); + } + return $this->sendWithMailers($mailers); } diff --git a/src/docs/user/configuration/configuring_outbound_email.diviner b/src/docs/user/configuration/configuring_outbound_email.diviner index 21abf92736..d2daf7a40a 100644 --- a/src/docs/user/configuration/configuring_outbound_email.diviner +++ b/src/docs/user/configuration/configuring_outbound_email.diviner @@ -12,6 +12,7 @@ including a local mailer or various third-party services. Options include: | Send Mail With | Setup | Cost | Inbound | Notes | |---------|-------|------|---------|-------| | Mailgun | Easy | Cheap | Yes | Recommended | +| Postmark | Easy | Cheap | Yes | Recommended | | Amazon SES | Easy | Cheap | No | Recommended | | SendGrid | Medium | Cheap | Yes | Discouraged | | External SMTP | Medium | Varies | No | Gmail, etc. | @@ -147,6 +148,17 @@ To use this mailer, set `type` to `mailgun`, then configure these `options`: - `domain`: Required string. Your Mailgun domain. +Mailer: Postmark +================ + +Postmark is a third-party email delivery serivice. You can learn more at +. + +To use this mailer, set `type` to `postmark`, then configure these `options`: + + - `access-token`: Required string. Your Postmark access token. + + Mailer: Amazon SES ==================