Skip to content

Commit dbedb01

Browse files
author
epriestley
committed
Add support for SendGrid as an outbound mail adapter
Summary: SendGrid is a popular mail delivery platform, similar to Amazon SES. Provide support for delivering email via their REST API. Test Plan: Created a SendGrid account, configured my local install to use it, sent some mail, received mail. Reviewers: tuomaspelkonen, jungejason, aran CC: ccheever Differential Revision: 347
1 parent 686ffaf commit dbedb01

File tree

5 files changed

+218
-6
lines changed

5 files changed

+218
-6
lines changed

conf/default.conf.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@
143143
'amazon-ses.access-key' => null,
144144
'amazon-ses.secret-key' => null,
145145

146+
// If you're using Sendgrid to send email, provide your access credentials
147+
// here. This will use the REST API. You can also use Sendgrid as a normal
148+
// SMTP service.
149+
'sendgrid.api-user' => null,
150+
'sendgrid.api-key' => null,
151+
146152
// You can configure a reply handler domain so that email sent from Maniphest
147153
// will have a special "Reply To" address like "T123+82+af19f@example.com"
148154
// that allows recipients to reply by email and interact with tasks. For

src/__phutil_library_map__.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@
339339
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base',
340340
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses',
341341
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite',
342+
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/sendgrid',
342343
'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test',
343344
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/base',
344345
'PhabricatorMetaMTAController' => 'applications/metamta/controller/base',
@@ -772,6 +773,7 @@
772773
'PhabricatorLogoutController' => 'PhabricatorAuthController',
773774
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
774775
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
776+
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
775777
'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
776778
'PhabricatorMetaMTAController' => 'PhabricatorController',
777779
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2011 Facebook, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
/**
20+
* Mail adapter that uses SendGrid's web API to deliver email.
21+
*/
22+
class PhabricatorMailImplementationSendGridAdapter
23+
extends PhabricatorMailImplementationAdapter {
24+
25+
private $params = array();
26+
27+
public function setFrom($email, $name = '') {
28+
$this->params['from'] = $email;
29+
$this->params['from-name'] = $name;
30+
return $this;
31+
}
32+
33+
public function addReplyTo($email, $name = '') {
34+
if (empty($this->params['reply-to'])) {
35+
$this->params['reply-to'] = array();
36+
}
37+
$this->params['reply-to'][] = array(
38+
'email' => $email,
39+
'name' => $name,
40+
);
41+
return $this;
42+
}
43+
44+
public function addTos(array $emails) {
45+
foreach ($emails as $email) {
46+
$this->params['tos'][] = $email;
47+
}
48+
return $this;
49+
}
50+
51+
public function addCCs(array $emails) {
52+
foreach ($emails as $email) {
53+
$this->params['ccs'][] = $email;
54+
}
55+
return $this;
56+
}
57+
58+
public function addHeader($header_name, $header_value) {
59+
$this->params['headers'][] = array($header_name, $header_value);
60+
return $this;
61+
}
62+
63+
public function setBody($body) {
64+
$this->params['body'] = $body;
65+
return $this;
66+
}
67+
68+
public function setSubject($subject) {
69+
$this->params['subject'] = $subject;
70+
return $this;
71+
}
72+
73+
public function setIsHTML($is_html) {
74+
$this->params['is-html'] = $is_html;
75+
return $this;
76+
}
77+
78+
public function supportsMessageIDHeader() {
79+
return false;
80+
}
81+
82+
public function send() {
83+
84+
$user = PhabricatorEnv::getEnvConfig('sendgrid.api-user');
85+
$key = PhabricatorEnv::getEnvConfig('sendgrid.api-key');
86+
87+
if (!$user || !$key) {
88+
throw new Exception(
89+
"Configure 'sendgrid.api-user' and 'sendgrid.api-key' to use ".
90+
"SendGrid for mail delivery.");
91+
}
92+
93+
$params = array();
94+
95+
$ii = 0;
96+
foreach (idx($this->params, 'tos', array()) as $to) {
97+
$params['to['.($ii++).']'] = $to;
98+
}
99+
100+
$params['subject'] = idx($this->params, 'subject');
101+
if (idx($this->params, 'is-html')) {
102+
$params['html'] = idx($this->params, 'body');
103+
} else {
104+
$params['text'] = idx($this->params, 'body');
105+
}
106+
107+
$params['from'] = idx($this->params, 'from');
108+
if (idx($this->params['from-name'])) {
109+
$params['fromname'] = idx($this->params, 'fromname');
110+
}
111+
112+
if (idx($this->params, 'replyto')) {
113+
$replyto = $this->params['replyto'];
114+
115+
// Pick off the email part, no support for the name part in this API.
116+
$params['replyto'] = $replyto[0];
117+
}
118+
119+
$headers = idx($this->params, 'headers', array());
120+
121+
// See SendGrid Support Ticket #29390; there's no explicit REST API support
122+
// for CC right now but it works if you add a generic "Cc" header.
123+
//
124+
// SendGrid said this is supported:
125+
// "You can use CC as you are trying to do there [by adding a generic
126+
// header]. It is supported despite our limited documentation to this
127+
// effect, I am glad you were able to figure it out regardless. ..."
128+
if (idx($this->params, 'ccs')) {
129+
$headers[] = array('Cc', implode(', ', $this->params['ccs']));
130+
}
131+
132+
if ($headers) {
133+
// Convert to dictionary.
134+
$headers = ipull($headers, 1, 0);
135+
$headers = json_encode($headers);
136+
$params['headers'] = $headers;
137+
}
138+
139+
$params['api_user'] = $user;
140+
$params['api_key'] = $key;
141+
142+
$future = new HTTPSFuture(
143+
'https://sendgrid.com/api/mail.send.json',
144+
$params);
145+
146+
list($code, $body) = $future->resolve();
147+
148+
if ($code !== 200) {
149+
throw new Exception("REST API call failed with HTTP code {$code}.");
150+
}
151+
152+
$response = json_decode($body, true);
153+
if (!is_array($response)) {
154+
throw new Exception("Failed to JSON decode response: {$body}");
155+
}
156+
157+
if ($response['message'] !== 'success') {
158+
$errors = implode(";", $response['errors']);
159+
throw new Exception("Request failed with errors: {$errors}.");
160+
}
161+
162+
return true;
163+
}
164+
165+
}
166+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
/**
3+
* This file is automatically generated. Lint this module to rebuild it.
4+
* @generated
5+
*/
6+
7+
8+
9+
phutil_require_module('phabricator', 'applications/metamta/adapter/base');
10+
phutil_require_module('phabricator', 'infrastructure/env');
11+
12+
phutil_require_module('phutil', 'future/https');
13+
phutil_require_module('phutil', 'utils');
14+
15+
16+
phutil_require_source('PhabricatorMailImplementationSendGridAdapter.php');

src/docs/configuring_outbound_email.diviner

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ Instructions for configuring Phabricator to send mail.
55

66
= Overview =
77

8-
Phabricator can send outbound email via four different adapters:
8+
Phabricator can send outbound email via several different adapters:
99

10-
- by running ##sendmail## on the local host; or
10+
- by running ##sendmail## on the local host with SMTP; or
1111
- by using Amazon SES (Simple Email Service); or
12+
- by using SendGrid's REST API; or
1213
- via a custom adapter you write; or
1314
- by dropping email into a hole and not delivering it.
1415

15-
Of these, ##sendmail## is the default but requires some configuration. SES is
16-
the easiest but costs money and has some limitations. Writing a custom solution
17-
requires digging into the code. See below for details on how to set up each
18-
method.
16+
Of these, ##sendmail## is the default but requires some configuration. SES and
17+
SendGrid are easier, but cost money and have some limitations. Writing a custom
18+
solution requires digging into the code. See below for details on how to set up
19+
each method.
1920

2021
Phabricator can also send outbound email in two ways:
2122

@@ -50,6 +51,8 @@ your configuration. Possible values are:
5051
"sendmail", see "Adapter: Sendmail".
5152
- ##PhabricatorMailImplementationAmazonSESAdapter##: use Amazon SES, see
5253
"Adapter: Amazon SES".
54+
- ##PhabricatorMailImplementationSendGridAdapter##: use SendGrid, see
55+
"Adapter: SendGrid".
5356
- ##Some Custom Class You Write##: use a custom adapter you write, see
5457
"Adapter: Custom".
5558
- ##PhabricatorMailImplementationTestAdapter##: this will
@@ -90,6 +93,25 @@ NOTE: Amazon SES is slow to accept mail (often 1-2 seconds) and application
9093
performance will improve greatly if you configure outbound email to send in
9194
the background.
9295

96+
= Adapter: SendGrid =
97+
98+
SendGrid is an email delivery service like Amazon SES. You can learn more at
99+
<http://sendgrid.com/>. It is easy to configure, but not free.
100+
101+
You can configure SendGrid in two ways: you can send via SMTP or via the REST
102+
API. To use SMTP, just configure ##sendmail## and leave Phabricator's setup
103+
with defaults. To use the REST API, follow the instructions in this section.
104+
105+
To configure Phabricator to use SendGrid, set these configuration keys:
106+
107+
- **metamta.mail-adapter**: set to
108+
"PhabricatorMailImplementationSendGridAdapter".
109+
- **sendgrid.api-user**: set to your SendGrid login name.
110+
- **sendgrid.api-key**: set to your SendGrid password.
111+
112+
If you're logged into your SendGrid account, you may be able to find this
113+
information easily by visiting <http://sendgrid.com/developer>.
114+
93115
= Adapter: Custom =
94116

95117
You can provide a custom adapter by writing a concrete subclass of

0 commit comments

Comments
 (0)