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

Upgrade Sendgrid to the modern mailer API; removes "api-user" option

Summary:
Ref T920. Ref T5969.

- Update to the new "$message" API.
- Update to Sendgrid v3.
- Add a timeout.
- This removes the "api-user" option, which Sendgrid no longer seems to use.

Test Plan: Sent Sendgrid messages with `bin/mail send-test ...` using subject/headers/attachments/html/to/cc.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: jbrownEP

Maniphest Tasks: T5969, T920

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

+88 -117
+88 -117
src/applications/metamta/adapter/PhabricatorMailSendGridAdapter.php
··· 8 8 9 9 const ADAPTERTYPE = 'sendgrid'; 10 10 11 - private $params = array(); 11 + public function getSupportedMessageTypes() { 12 + return array( 13 + PhabricatorMailEmailMessage::MESSAGETYPE, 14 + ); 15 + } 12 16 13 17 protected function validateOptions(array $options) { 14 18 PhutilTypeSpec::checkMap( 15 19 $options, 16 20 array( 17 - 'api-user' => 'string', 18 21 'api-key' => 'string', 19 22 )); 20 23 } 21 24 22 25 public function newDefaultOptions() { 23 26 return array( 24 - 'api-user' => null, 25 27 'api-key' => null, 26 28 ); 27 29 } 28 30 29 - public function setFrom($email, $name = '') { 30 - $this->params['from'] = $email; 31 - $this->params['from-name'] = $name; 32 - return $this; 33 - } 31 + public function sendMessage(PhabricatorMailExternalMessage $message) { 32 + $key = $this->getOption('api-key'); 34 33 35 - public function addReplyTo($email, $name = '') { 36 - if (empty($this->params['reply-to'])) { 37 - $this->params['reply-to'] = array(); 38 - } 39 - $this->params['reply-to'][] = array( 40 - 'email' => $email, 41 - 'name' => $name, 42 - ); 43 - return $this; 44 - } 34 + $parameters = array(); 45 35 46 - public function addTos(array $emails) { 47 - foreach ($emails as $email) { 48 - $this->params['tos'][] = $email; 36 + $subject = $message->getSubject(); 37 + if ($subject !== null) { 38 + $parameters['subject'] = $subject; 49 39 } 50 - return $this; 51 - } 52 40 53 - public function addCCs(array $emails) { 54 - foreach ($emails as $email) { 55 - $this->params['ccs'][] = $email; 56 - } 57 - return $this; 58 - } 41 + $personalizations = array(); 59 42 60 - public function addAttachment($data, $filename, $mimetype) { 61 - if (empty($this->params['files'])) { 62 - $this->params['files'] = array(); 43 + $to_addresses = $message->getToAddresses(); 44 + if ($to_addresses) { 45 + $personalizations['to'] = array(); 46 + foreach ($to_addresses as $address) { 47 + $personalizations['to'][] = $this->newPersonalization($address); 48 + } 63 49 } 64 - $this->params['files'][$filename] = $data; 65 - } 66 50 67 - public function addHeader($header_name, $header_value) { 68 - $this->params['headers'][] = array($header_name, $header_value); 69 - return $this; 70 - } 71 - 72 - public function setBody($body) { 73 - $this->params['body'] = $body; 74 - return $this; 75 - } 76 - 77 - public function setHTMLBody($body) { 78 - $this->params['html-body'] = $body; 79 - return $this; 80 - } 81 - 82 - 83 - public function setSubject($subject) { 84 - $this->params['subject'] = $subject; 85 - return $this; 86 - } 87 - 88 - public function supportsMessageIDHeader() { 89 - return false; 90 - } 91 - 92 - public function send() { 93 - $user = $this->getOption('api-user'); 94 - $key = $this->getOption('api-key'); 95 - 96 - $params = array(); 97 - 98 - $ii = 0; 99 - foreach (idx($this->params, 'tos', array()) as $to) { 100 - $params['to['.($ii++).']'] = $to; 51 + $cc_addresses = $message->getCCAddresses(); 52 + if ($cc_addresses) { 53 + $personalizations['cc'] = array(); 54 + foreach ($cc_addresses as $address) { 55 + $personalizations['cc'][] = $this->newPersonalization($address); 56 + } 101 57 } 102 58 103 - $params['subject'] = idx($this->params, 'subject'); 104 - $params['text'] = idx($this->params, 'body'); 59 + // This is a list of different sets of recipients who should receive copies 60 + // of the mail. We handle "one message to each recipient" ourselves. 61 + $parameters['personalizations'] = array( 62 + $personalizations, 63 + ); 105 64 106 - if (idx($this->params, 'html-body')) { 107 - $params['html'] = idx($this->params, 'html-body'); 65 + $from_address = $message->getFromAddress(); 66 + if ($from_address) { 67 + $parameters['from'] = $this->newPersonalization($from_address); 108 68 } 109 69 110 - $params['from'] = idx($this->params, 'from'); 111 - if (idx($this->params, 'from-name')) { 112 - $params['fromname'] = $this->params['from-name']; 70 + $reply_address = $message->getReplyToAddress(); 71 + if ($reply_address) { 72 + $parameters['reply_to'] = $this->newPersonalization($reply_address); 113 73 } 114 74 115 - if (idx($this->params, 'reply-to')) { 116 - $replyto = $this->params['reply-to']; 117 - 118 - // Pick off the email part, no support for the name part in this API. 119 - $params['replyto'] = $replyto[0]['email']; 75 + $headers = $message->getHeaders(); 76 + if ($headers) { 77 + $map = array(); 78 + foreach ($headers as $header) { 79 + $map[$header->getName()] = $header->getValue(); 80 + } 81 + $parameters['headers'] = $map; 120 82 } 121 83 122 - foreach (idx($this->params, 'files', array()) as $name => $data) { 123 - $params['files['.$name.']'] = $data; 84 + $content = array(); 85 + $text_body = $message->getTextBody(); 86 + if ($text_body !== null) { 87 + $content[] = array( 88 + 'type' => 'text/plain', 89 + 'value' => $text_body, 90 + ); 124 91 } 125 92 126 - $headers = idx($this->params, 'headers', array()); 127 - 128 - // See SendGrid Support Ticket #29390; there's no explicit REST API support 129 - // for CC right now but it works if you add a generic "Cc" header. 130 - // 131 - // SendGrid said this is supported: 132 - // "You can use CC as you are trying to do there [by adding a generic 133 - // header]. It is supported despite our limited documentation to this 134 - // effect, I am glad you were able to figure it out regardless. ..." 135 - if (idx($this->params, 'ccs')) { 136 - $headers[] = array('Cc', implode(', ', $this->params['ccs'])); 93 + $html_body = $message->getHTMLBody(); 94 + if ($html_body !== null) { 95 + $content[] = array( 96 + 'type' => 'text/html', 97 + 'value' => $html_body, 98 + ); 137 99 } 100 + $parameters['content'] = $content; 138 101 139 - if ($headers) { 140 - // Convert to dictionary. 141 - $headers = ipull($headers, 1, 0); 142 - $headers = json_encode($headers); 143 - $params['headers'] = $headers; 102 + $attachments = $message->getAttachments(); 103 + if ($attachments) { 104 + $files = array(); 105 + foreach ($attachments as $attachment) { 106 + $files[] = array( 107 + 'content' => base64_encode($attachment->getData()), 108 + 'type' => $attachment->getMimeType(), 109 + 'filename' => $attachment->getFilename(), 110 + 'disposition' => 'attachment', 111 + ); 112 + } 113 + $parameters['attachments'] = $files; 144 114 } 145 115 146 - $params['api_user'] = $user; 147 - $params['api_key'] = $key; 116 + $sendgrid_uri = 'https://api.sendgrid.com/v3/mail/send'; 117 + $json_parameters = phutil_json_encode($parameters); 148 118 149 - $future = new HTTPSFuture( 150 - 'https://sendgrid.com/api/mail.send.json', 151 - $params); 152 - $future->setMethod('POST'); 119 + id(new HTTPSFuture($sendgrid_uri)) 120 + ->setMethod('POST') 121 + ->addHeader('Authorization', "Bearer {$key}") 122 + ->addHeader('Content-Type', 'application/json') 123 + ->setData($json_parameters) 124 + ->setTimeout(60) 125 + ->resolvex(); 153 126 154 - list($body) = $future->resolvex(); 127 + // The SendGrid v3 API does not return a JSON response body. We get a 128 + // non-2XX HTTP response in the case of an error, which throws above. 129 + } 155 130 156 - $response = null; 157 - try { 158 - $response = phutil_json_decode($body); 159 - } catch (PhutilJSONParserException $ex) { 160 - throw new PhutilProxyException( 161 - pht('Failed to JSON decode response.'), 162 - $ex); 163 - } 131 + private function newPersonalization(PhutilEmailAddress $address) { 132 + $result = array( 133 + 'email' => $address->getAddress(), 134 + ); 164 135 165 - if ($response['message'] !== 'success') { 166 - $errors = implode(';', $response['errors']); 167 - throw new Exception(pht('Request failed with errors: %s.', $errors)); 136 + $display_name = $address->getDisplayName(); 137 + if ($display_name) { 138 + $result['name'] = $display_name; 168 139 } 169 140 170 - return true; 141 + return $result; 171 142 } 172 143 173 144 }