diff --git a/src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php b/src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php --- a/src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php +++ b/src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php @@ -8,166 +8,137 @@ const ADAPTERTYPE = 'sendgrid'; - private $params = array(); + public function getSupportedMessageTypes() { + return array( + PhabricatorMailEmailMessage::MESSAGETYPE, + ); + } protected function validateOptions(array $options) { PhutilTypeSpec::checkMap( $options, array( - 'api-user' => 'string', 'api-key' => 'string', )); } public function newDefaultOptions() { return array( - 'api-user' => null, 'api-key' => null, ); } - public function setFrom($email, $name = '') { - $this->params['from'] = $email; - $this->params['from-name'] = $name; - return $this; - } + public function sendMessage(PhabricatorMailExternalMessage $message) { + $key = $this->getOption('api-key'); - public function addReplyTo($email, $name = '') { - if (empty($this->params['reply-to'])) { - $this->params['reply-to'] = array(); - } - $this->params['reply-to'][] = array( - 'email' => $email, - 'name' => $name, - ); - return $this; - } + $parameters = array(); - public function addTos(array $emails) { - foreach ($emails as $email) { - $this->params['tos'][] = $email; + $subject = $message->getSubject(); + if ($subject !== null) { + $parameters['subject'] = $subject; } - return $this; - } - public function addCCs(array $emails) { - foreach ($emails as $email) { - $this->params['ccs'][] = $email; - } - return $this; - } + $personalizations = array(); - public function addAttachment($data, $filename, $mimetype) { - if (empty($this->params['files'])) { - $this->params['files'] = array(); + $to_addresses = $message->getToAddresses(); + if ($to_addresses) { + $personalizations['to'] = array(); + foreach ($to_addresses as $address) { + $personalizations['to'][] = $this->newPersonalization($address); + } } - $this->params['files'][$filename] = $data; - } - - public function addHeader($header_name, $header_value) { - $this->params['headers'][] = array($header_name, $header_value); - return $this; - } - - public function setBody($body) { - $this->params['body'] = $body; - return $this; - } - - public function setHTMLBody($body) { - $this->params['html-body'] = $body; - return $this; - } - - - public function setSubject($subject) { - $this->params['subject'] = $subject; - return $this; - } - - public function supportsMessageIDHeader() { - return false; - } - - public function send() { - $user = $this->getOption('api-user'); - $key = $this->getOption('api-key'); - - $params = array(); - $ii = 0; - foreach (idx($this->params, 'tos', array()) as $to) { - $params['to['.($ii++).']'] = $to; + $cc_addresses = $message->getCCAddresses(); + if ($cc_addresses) { + $personalizations['cc'] = array(); + foreach ($cc_addresses as $address) { + $personalizations['cc'][] = $this->newPersonalization($address); + } } - $params['subject'] = idx($this->params, 'subject'); - $params['text'] = idx($this->params, 'body'); + // This is a list of different sets of recipients who should receive copies + // of the mail. We handle "one message to each recipient" ourselves. + $parameters['personalizations'] = array( + $personalizations, + ); - if (idx($this->params, 'html-body')) { - $params['html'] = idx($this->params, 'html-body'); + $from_address = $message->getFromAddress(); + if ($from_address) { + $parameters['from'] = $this->newPersonalization($from_address); } - $params['from'] = idx($this->params, 'from'); - if (idx($this->params, 'from-name')) { - $params['fromname'] = $this->params['from-name']; + $reply_address = $message->getReplyToAddress(); + if ($reply_address) { + $parameters['reply_to'] = $this->newPersonalization($reply_address); } - if (idx($this->params, 'reply-to')) { - $replyto = $this->params['reply-to']; - - // Pick off the email part, no support for the name part in this API. - $params['replyto'] = $replyto[0]['email']; + $headers = $message->getHeaders(); + if ($headers) { + $map = array(); + foreach ($headers as $header) { + $map[$header->getName()] = $header->getValue(); + } + $parameters['headers'] = $map; } - foreach (idx($this->params, 'files', array()) as $name => $data) { - $params['files['.$name.']'] = $data; + $content = array(); + $text_body = $message->getTextBody(); + if ($text_body !== null) { + $content[] = array( + 'type' => 'text/plain', + 'value' => $text_body, + ); } - $headers = idx($this->params, 'headers', array()); - - // See SendGrid Support Ticket #29390; there's no explicit REST API support - // for CC right now but it works if you add a generic "Cc" header. - // - // SendGrid said this is supported: - // "You can use CC as you are trying to do there [by adding a generic - // header]. It is supported despite our limited documentation to this - // effect, I am glad you were able to figure it out regardless. ..." - if (idx($this->params, 'ccs')) { - $headers[] = array('Cc', implode(', ', $this->params['ccs'])); + $html_body = $message->getHTMLBody(); + if ($html_body !== null) { + $content[] = array( + 'type' => 'text/html', + 'value' => $html_body, + ); } - - if ($headers) { - // Convert to dictionary. - $headers = ipull($headers, 1, 0); - $headers = json_encode($headers); - $params['headers'] = $headers; + $parameters['content'] = $content; + + $attachments = $message->getAttachments(); + if ($attachments) { + $files = array(); + foreach ($attachments as $attachment) { + $files[] = array( + 'content' => base64_encode($attachment->getData()), + 'type' => $attachment->getMimeType(), + 'filename' => $attachment->getFilename(), + 'disposition' => 'attachment', + ); + } + $parameters['attachments'] = $files; } - $params['api_user'] = $user; - $params['api_key'] = $key; + $sendgrid_uri = 'https://api.sendgrid.com/v3/mail/send'; + $json_parameters = phutil_json_encode($parameters); - $future = new HTTPSFuture( - 'https://sendgrid.com/api/mail.send.json', - $params); - $future->setMethod('POST'); + id(new HTTPSFuture($sendgrid_uri)) + ->setMethod('POST') + ->addHeader('Authorization', "Bearer {$key}") + ->addHeader('Content-Type', 'application/json') + ->setData($json_parameters) + ->setTimeout(60) + ->resolvex(); - list($body) = $future->resolvex(); + // The SendGrid v3 API does not return a JSON response body. We get a + // non-2XX HTTP response in the case of an error, which throws above. + } - $response = null; - try { - $response = phutil_json_decode($body); - } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( - pht('Failed to JSON decode response.'), - $ex); - } + private function newPersonalization(PhutilEmailAddress $address) { + $result = array( + 'email' => $address->getAddress(), + ); - if ($response['message'] !== 'success') { - $errors = implode(';', $response['errors']); - throw new Exception(pht('Request failed with errors: %s.', $errors)); + $display_name = $address->getDisplayName(); + if ($display_name) { + $result['name'] = $display_name; } - return true; + return $result; } }