diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3188,6 +3188,7 @@ '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 @@ '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 --- a/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php @@ -94,4 +94,13 @@ 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 --- /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 --- a/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php +++ b/src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php @@ -47,6 +47,11 @@ '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 @@ $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 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -319,6 +319,10 @@ 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 @@ $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 --- a/src/docs/user/configuration/configuring_outbound_email.diviner +++ b/src/docs/user/configuration/configuring_outbound_email.diviner @@ -12,6 +12,7 @@ | 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 @@ - `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 ==================