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

Introduce and document a new `cluster.mailers` option for configuring multiple mailers

Summary:
Depends on D19002. Ref T13053. Ref T12677. Adds a new option to allow configuration of multiple mailers.

Nothing actually uses this yet.

Test Plan: Tried to set it to various bad values, got reasonable error messages. Read documentation.

Reviewers: amckinley

Maniphest Tasks: T13053, T12677

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

+315 -118
+2
src/__phutil_library_map__.php
··· 2411 2411 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php', 2412 2412 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php', 2413 2413 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php', 2414 + 'PhabricatorClusterMailersConfigType' => 'infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php', 2414 2415 'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php', 2415 2416 'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php', 2416 2417 'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php', ··· 7824 7825 'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler', 7825 7826 'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException', 7826 7827 'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException', 7828 + 'PhabricatorClusterMailersConfigType' => 'PhabricatorJSONConfigType', 7827 7829 'PhabricatorClusterNoHostForRoleException' => 'Exception', 7828 7830 'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType', 7829 7831 'PhabricatorClusterServiceHealthRecord' => 'Phobject',
+13 -7
src/applications/config/option/PhabricatorMetaMTAConfigOptions.php
··· 138 138 , 139 139 'metamta.public-replies')); 140 140 141 - $adapter_doc_href = PhabricatorEnv::getDoclink( 142 - 'Configuring Outbound Email'); 143 - $adapter_doc_name = pht('Configuring Outbound Email'); 144 141 $adapter_description = $this->deformat(pht(<<<EODOC 145 142 Adapter class to use to transmit mail to the MTA. The default uses 146 143 PHPMailerLite, which will invoke "sendmail". This is appropriate if sendmail 147 144 actually works on your host, but if you haven't configured mail it may not be so 148 145 great. A number of other mailers are available (e.g., SES, SendGrid, SMTP, 149 - custom mailers) - consult [[ %s | %s ]] for details. 146 + custom mailers). This option is deprecated in favor of 'cluster.mailers'. 150 147 EODOC 151 - , 152 - $adapter_doc_href, 153 - $adapter_doc_name)); 148 + )); 154 149 155 150 $placeholder_description = $this->deformat(pht(<<<EODOC 156 151 When sending a message that has no To recipient (i.e. all recipients are CC'd), ··· 197 192 EODOC 198 193 )); 199 194 195 + $mailers_description = $this->deformat(pht(<<<EODOC 196 + Define one or more mail transmission services. For help with configuring 197 + mailers, see **[[ %s | %s ]]** in the documentation. 198 + EODOC 199 + , 200 + PhabricatorEnv::getDoclink('Configuring Outbound Email'), 201 + pht('Configuring Outbound Email'))); 202 + 200 203 return array( 204 + $this->newOption('cluster.mailers', 'cluster.mailers', null) 205 + ->setLocked(true) 206 + ->setDescription($mailers_description), 201 207 $this->newOption( 202 208 'metamta.default-address', 203 209 'string',
+12
src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
··· 5 5 private $key; 6 6 private $options = array(); 7 7 8 + final public function getAdapterType() { 9 + return $this->getPhobjectClassConstant('ADAPTERTYPE'); 10 + } 11 + 12 + final public static function getAllAdapters() { 13 + return id(new PhutilClassMapQuery()) 14 + ->setAncestorClass(__CLASS__) 15 + ->setUniqueMethod('getAdapterType') 16 + ->execute(); 17 + } 18 + 19 + 8 20 abstract public function setFrom($email, $name = ''); 9 21 abstract public function addReplyTo($email, $name = ''); 10 22 abstract public function addTos(array $emails);
+2
src/applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php
··· 3 3 final class PhabricatorMailImplementationAmazonSESAdapter 4 4 extends PhabricatorMailImplementationPHPMailerLiteAdapter { 5 5 6 + const ADAPTERTYPE = 'ses'; 7 + 6 8 private $message; 7 9 private $isHTML; 8 10
+2
src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php
··· 6 6 final class PhabricatorMailImplementationMailgunAdapter 7 7 extends PhabricatorMailImplementationAdapter { 8 8 9 + const ADAPTERTYPE = 'mailgun'; 10 + 9 11 private $params = array(); 10 12 private $attachments = array(); 11 13
+2
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php
··· 3 3 final class PhabricatorMailImplementationPHPMailerAdapter 4 4 extends PhabricatorMailImplementationAdapter { 5 5 6 + const ADAPTERTYPE = 'smtp'; 7 + 6 8 private $mailer; 7 9 8 10 protected function validateOptions(array $options) {
+2
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php
··· 6 6 class PhabricatorMailImplementationPHPMailerLiteAdapter 7 7 extends PhabricatorMailImplementationAdapter { 8 8 9 + const ADAPTERTYPE = 'sendmail'; 10 + 9 11 protected $mailer; 10 12 11 13 protected function validateOptions(array $options) {
+2
src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php
··· 6 6 final class PhabricatorMailImplementationSendGridAdapter 7 7 extends PhabricatorMailImplementationAdapter { 8 8 9 + const ADAPTERTYPE = 'sendgrid'; 10 + 9 11 private $params = array(); 10 12 11 13 protected function validateOptions(array $options) {
+2
src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php
··· 7 7 final class PhabricatorMailImplementationTestAdapter 8 8 extends PhabricatorMailImplementationAdapter { 9 9 10 + const ADAPTERTYPE = 'test'; 11 + 10 12 private $guts = array(); 11 13 private $config = array(); 12 14
+176 -111
src/docs/user/configuration/configuring_outbound_email.diviner
··· 3 3 4 4 Instructions for configuring Phabricator to send mail. 5 5 6 - = Overview = 6 + Overview 7 + ======== 7 8 8 - Phabricator can send outbound email via several different providers, called 9 - "Adapters". 9 + Phabricator can send outbound email through several different mail services, 10 + including a local mailer or various third-party services. Options include: 10 11 11 12 | Send Mail With | Setup | Cost | Inbound | Notes | 12 13 |---------|-------|------|---------|-------| 13 14 | Mailgun | Easy | Cheap | Yes | Recommended | 14 15 | Amazon SES | Easy | Cheap | No | Recommended | 15 - | SendGrid | Medium | Cheap | Yes | Discouraged (See Note) | 16 + | SendGrid | Medium | Cheap | Yes | Discouraged | 16 17 | External SMTP | Medium | Varies | No | Gmail, etc. | 17 - | Local SMTP | Hard | Free | No | (Default) sendmail, postfix, etc | 18 - | Custom | Hard | Free | No | Write an adapter for some other service. | 18 + | Local SMTP | Hard | Free | No | sendmail, postfix, etc | 19 + | Custom | Hard | Free | No | Write a custom mailer for some other service. | 19 20 | Drop in a Hole | Easy | Free | No | Drops mail in a deep, dark hole. | 20 21 21 - Of these options, sending mail via local SMTP is the default, but usually 22 - requires some configuration to get working. See below for details on how to 23 - select and configure a delivery method. 22 + See below for details on how to select and configure mail delivery for each 23 + mailer. 24 24 25 25 Overall, Mailgun and SES are much easier to set up, and using one of them is 26 26 recommended. In particular, Mailgun will also let you set up inbound email 27 27 easily. 28 28 29 29 If you have some internal mail service you'd like to use you can also 30 - write a custom adapter, but this requires digging into the code. 30 + write a custom mailer, but this requires digging into the code. 31 31 32 32 Phabricator sends mail in the background, so the daemons need to be running for 33 33 it to be able to deliver mail. You should receive setup warnings if they are 34 34 not. For more information on using daemons, see 35 35 @{article:Managing Daemons with phd}. 36 36 37 - **Note on SendGrid**: Users have experienced a number of odd issues with 38 - SendGrid, compared to fewer issues with other mailers. We discourage SendGrid 39 - unless you're already using it. If you send to SendGrid via SMTP, you may need 40 - to adjust `phpmailer.smtp-encoding`. 41 37 42 - = Basics = 38 + Basics 39 + ====== 43 40 44 41 Regardless of how outbound email is delivered, you should configure these keys 45 42 in your configuration: ··· 51 48 - **metamta.can-send-as-user** should be left as `false` in most cases, 52 49 but see the documentation for details. 53 50 54 - = Configuring Mail Adapters = 55 51 56 - To choose how mail will be sent, change the `metamta.mail-adapter` key in 57 - your configuration. Possible values are listed in the UI: 52 + Configuring Mailers 53 + =================== 58 54 59 - - `PhabricatorMailImplementationAmazonMailgunAdapter`: use Mailgun, see 60 - "Adapter: Mailgun". 61 - - `PhabricatorMailImplementationAmazonSESAdapter`: use Amazon SES, see 62 - "Adapter: Amazon SES". 63 - - `PhabricatorMailImplementationPHPMailerLiteAdapter`: default, uses 64 - "sendmail", see "Adapter: Sendmail". 65 - - `PhabricatorMailImplementationPHPMailerAdapter`: uses SMTP, see 66 - "Adapter: SMTP". 67 - - `PhabricatorMailImplementationSendGridAdapter`: use SendGrid, see 68 - "Adapter: SendGrid". 69 - - `Some Custom Class You Write`: use a custom adapter you write, see 70 - "Adapter: Custom". 71 - - `PhabricatorMailImplementationTestAdapter`: this will 72 - **completely disable** outbound mail. You can use this if you don't want to 73 - send outbound mail, or want to skip this step for now and configure it 74 - later. 55 + Configure one or more mailers by listing them in the the `cluster.mailers` 56 + configuration option. Most installs only need to configure one mailer, but you 57 + can configure multiple mailers to provide greater availability in the event of 58 + a service disruption. 75 59 76 - = Adapter: Sendmail = 60 + A valid `cluster.mailers` configuration looks something like this: 77 61 78 - This is the default, and selected by choosing 79 - `PhabricatorMailImplementationPHPMailerLiteAdapter` as the value for 80 - **metamta.mail-adapter**. This requires a `sendmail` binary to be installed on 81 - the system. Most MTAs (e.g., sendmail, qmail, postfix) should do this, but your 82 - machine may not have one installed by default. For install instructions, consult 83 - the documentation for your favorite MTA. 62 + ```lang=json 63 + [ 64 + { 65 + "key": "mycompany-mailgun", 66 + "type": "mailgun", 67 + "options": { 68 + "domain": "mycompany.com", 69 + "api-key": "..." 70 + } 71 + }, 72 + ... 73 + ] 74 + ``` 84 75 85 - Since you'll be sending the mail yourself, you are subject to things like SPF 86 - rules, blackholes, and MTA configuration which are beyond the scope of this 87 - document. If you can already send outbound email from the command line or know 88 - how to configure it, this option is straightforward. If you have no idea how to 89 - do any of this, strongly consider using Mailgun or Amazon SES instead. 76 + The supported keys for each mailer are: 90 77 91 - If you experience issues with mail getting mangled (for example, arriving with 92 - too many or too few newlines) you may try adjusting `phpmailer.smtp-encoding`. 78 + - `key`: Required string. A unique name for this mailer. 79 + - `type`: Required string. Identifies the type of mailer. See below for 80 + options. 81 + - `priority`: Optional string. Advanced option which controls load balancing 82 + and failover behavior. See below for details. 83 + - `options`: Optional map. Additional options for the mailer type. 93 84 94 - = Adapter: SMTP = 85 + The `type` field can be used to select these third-party mailers: 95 86 96 - You can use this adapter to send mail via an external SMTP server, like Gmail. 97 - To do this, set these configuration keys: 87 + - `mailgun`: Use Mailgun. 88 + - `ses`: Use Amazon SES. 89 + - `sendgrid`: Use Sendgrid. 98 90 99 - - **metamta.mail-adapter**: set to 100 - `PhabricatorMailImplementationPHPMailerAdapter`. 101 - - **phpmailer.mailer**: set to `smtp`. 102 - - **phpmailer.smtp-host**: set to hostname of your SMTP server. 103 - - **phpmailer.smtp-port**: set to port of your SMTP server. 104 - - **phpmailer.smtp-user**: set to your username used for authentication. 105 - - **phpmailer.smtp-password**: set to your password used for authentication. 106 - - **phpmailer.smtp-protocol**: set to `tls` or `ssl` if necessary. Use 107 - `ssl` for Gmail. 108 - - **phpmailer.smtp-encoding**: Normally safe to leave as the default, but 109 - adjusting it may help resolve mail mangling issues (for example, mail 110 - arriving with too many or too few newlines). 91 + It also supports these local mailers: 111 92 112 - = Adapter: Mailgun = 93 + - `sendmail`: Use the local `sendmail` binary. 94 + - `smtp`: Connect directly to an SMTP server. 95 + - `test`: Internal mailer for testing. Does not send mail. 113 96 114 - Mailgun is an email delivery service. You can learn more at 115 - <http://www.mailgun.com>. Mailgun isn't free, but is very easy to configure 116 - and works well. 97 + You can also write your own mailer by extending 98 + `PhabricatorMailImplementationAdapter`. 117 99 118 - To use Mailgun, sign up for an account, then set these configuration keys: 100 + Once you've selected a mailer, find the corresponding section below for 101 + instructions on configuring it. 119 102 120 - - **metamta.mail-adapter**: set to 121 - `PhabricatorMailImplementationMailgunAdapter`. 122 - - **mailgun.api-key**: set to your Mailgun API key. 123 - - **mailgun.domain**: set to your Mailgun domain. 124 103 125 - = Adapter: Amazon SES = 104 + Mailer: Mailgun 105 + =============== 126 106 127 - Amazon SES is Amazon's cloud email service. It is not free, but is easier to 128 - configure than sendmail and can simplify outbound email configuration. To use 129 - Amazon SES, you need to sign up for an account with Amazon at 107 + Mailgun is a third-party email delivery service. You can learn more at 108 + <http://www.mailgun.com>. Mailgun is easy to configure and works well. 109 + 110 + To use this mailer, set `type` to `mailgun`, then configure these `options`: 111 + 112 + - `api-key`: Required string. Your Mailgun API key. 113 + - `domain`: Required string. Your Mailgun domain. 114 + 115 + 116 + Mailer: Amazon SES 117 + ================== 118 + 119 + Amazon SES is Amazon's cloud email service. You can learn more at 130 120 <http://aws.amazon.com/ses/>. 131 121 132 - To configure Phabricator to use Amazon SES, set these configuration keys: 122 + To use this mailer, set `type` to `ses`, then configure these `options`: 123 + 124 + - `access-key`: Required string. Your Amazon SES access key. 125 + - `secret-key`: Required string. Your Amazon SES secret key. 126 + - `endpoint`: Required string. Your Amazon SES endpoint. 133 127 134 - - **metamta.mail-adapter**: set to 135 - "PhabricatorMailImplementationAmazonSESAdapter". 136 - - **amazon-ses.access-key**: set to your Amazon SES access key. 137 - - **amazon-ses.secret-key**: set to your Amazon SES secret key. 138 - - **amazon-ses.endpoint**: Set to your Amazon SES endpoint. 128 + NOTE: Amazon SES **requires you to verify your "From" address**. Configure 129 + which "From" address to use by setting "`metamta.default-address`" in your 130 + config, then follow the Amazon SES verification process to verify it. You 131 + won't be able to send email until you do this! 139 132 140 - NOTE: Amazon SES **requires you to verify your "From" address**. Configure which 141 - "From" address to use by setting "`metamta.default-address`" in your config, 142 - then follow the Amazon SES verification process to verify it. You won't be able 143 - to send email until you do this! 144 133 145 - = Adapter: SendGrid = 134 + Mailer: SendGrid 135 + ================ 146 136 147 - SendGrid is an email delivery service like Amazon SES. You can learn more at 148 - <http://sendgrid.com/>. It is easy to configure, but not free. 137 + SendGrid is a third-party email delivery service. You can learn more at 138 + <http://sendgrid.com/>. 149 139 150 140 You can configure SendGrid in two ways: you can send via SMTP or via the REST 151 - API. To use SMTP, just configure `sendmail` and leave Phabricator's setup 152 - with defaults. To use the REST API, follow the instructions in this section. 141 + API. To use SMTP, configure Phabricator to use an `smtp` mailer. 153 142 154 - To configure Phabricator to use SendGrid, set these configuration keys: 143 + To use the REST API mailer, set `type` to `sendgrid`, then configure 144 + these `options`: 155 145 156 - - **metamta.mail-adapter**: set to 157 - "PhabricatorMailImplementationSendGridAdapter". 158 - - **sendgrid.api-user**: set to your SendGrid login name. 159 - - **sendgrid.api-key**: set to your SendGrid password. 146 + - `api-user`: Required string. Your SendGrid login name. 147 + - `api-key`: Required string. Your SendGrid API key. 160 148 161 - If you're logged into your SendGrid account, you may be able to find this 162 - information easily by visiting <http://sendgrid.com/developer>. 149 + NOTE: Users have experienced a number of odd issues with SendGrid, compared to 150 + fewer issues with other mailers. We discourage SendGrid unless you're already 151 + using it. 152 + 153 + 154 + Mailer: Sendmail 155 + ================ 163 156 164 - = Adapter: Custom = 157 + This requires a `sendmail` binary to be installed on 158 + the system. Most MTAs (e.g., sendmail, qmail, postfix) should do this, but your 159 + machine may not have one installed by default. For install instructions, consult 160 + the documentation for your favorite MTA. 161 + 162 + Since you'll be sending the mail yourself, you are subject to things like SPF 163 + rules, blackholes, and MTA configuration which are beyond the scope of this 164 + document. If you can already send outbound email from the command line or know 165 + how to configure it, this option is straightforward. If you have no idea how to 166 + do any of this, strongly consider using Mailgun or Amazon SES instead. 167 + 168 + To use this mailer, set `type` to `sendmail`. There are no `options` to 169 + configure. 170 + 171 + 172 + Mailer: STMP 173 + ============ 174 + 175 + You can use this adapter to send mail via an external SMTP server, like Gmail. 176 + 177 + To use this mailer, set `type` to `smtp`, then configure these `options`: 178 + 179 + - `host`: Required string. The hostname of your SMTP server. 180 + - `user`: Optional string. Username used for authentication. 181 + - `password`: Optional string. Password for authentication. 182 + - `protocol`: Optional string. Set to `tls` or `ssl` if necessary. Use 183 + `ssl` for Gmail. 165 184 166 - You can provide a custom adapter by writing a concrete subclass of 167 - @{class:PhabricatorMailImplementationAdapter} and setting it as the 168 - `metamta.mail-adapter`. 169 185 170 - TODO: This should be better documented once extending Phabricator is better 171 - documented. 186 + Disable Mail 187 + ============ 172 188 173 - = Adapter: Disable Outbound Mail = 189 + To disable mail, just don't configure any mailers. 174 190 175 - You can use the @{class:PhabricatorMailImplementationTestAdapter} to completely 176 - disable outbound mail, if you don't want to send mail or don't want to configure 177 - it yet. Just set **metamta.mail-adapter** to 178 - `PhabricatorMailImplementationTestAdapter`. 179 191 180 - = Testing and Debugging Outbound Email = 192 + Testing and Debugging Outbound Email 193 + ==================================== 181 194 182 195 You can use the `bin/mail` utility to test, debug, and examine outbound mail. In 183 196 particular: ··· 191 204 You can monitor daemons using the Daemon Console (`/daemon/`, or click 192 205 **Daemon Console** from the homepage). 193 206 194 - = Next Steps = 207 + 208 + Priorities 209 + ========== 210 + 211 + By default, Phabricator will try each mailer in order: it will try the first 212 + mailer first. If that fails (for example, because the service is not available 213 + at the moment) it will try the second mailer, and so on. 214 + 215 + If you want to load balance between multiple mailers instead of using one as 216 + a primary, you can set `priority`. Phabricator will start with mailers in the 217 + highest priority group and go through them randomly, then fall back to the 218 + next group. 219 + 220 + For example, if you have two SMTP servers and you want to balance requests 221 + between them and then fall back to Mailgun if both fail, configure priorities 222 + like this: 223 + 224 + ```lang=json 225 + [ 226 + { 227 + "key": "smtp-uswest", 228 + "type": "smtp", 229 + "priority": 300, 230 + "options": "..." 231 + }, 232 + { 233 + "key": "smtp-useast", 234 + "type": "smtp", 235 + "priority": 300, 236 + "options": "..." 237 + }, 238 + { 239 + "key": "mailgun-fallback", 240 + "type": "mailgun", 241 + "options": "..." 242 + } 243 + } 244 + ``` 245 + 246 + Phabricator will start with servers in the highest priority group (the group 247 + with the **largest** `priority` number). In this example, the highest group is 248 + `300`, which has the two SMTP servers. They'll be tried in random order first. 249 + 250 + If both fail, Phabricator will move on to the next priority group. In this 251 + example, there are no other priority groups. 252 + 253 + If it still hasn't sent the mail, Phabricator will try servers which are not 254 + in any priority group, in the configured order. In this example there is 255 + only one such server, so it will try to send via Mailgun. 256 + 257 + 258 + Next Steps 259 + ========== 195 260 196 261 Continue by: 197 262
+100
src/infrastructure/cluster/config/PhabricatorClusterMailersConfigType.php
··· 1 + <?php 2 + 3 + final class PhabricatorClusterMailersConfigType 4 + extends PhabricatorJSONConfigType { 5 + 6 + const TYPEKEY = 'cluster.mailers'; 7 + 8 + public function validateStoredValue( 9 + PhabricatorConfigOption $option, 10 + $value) { 11 + 12 + if ($value === null) { 13 + return; 14 + } 15 + 16 + if (!is_array($value)) { 17 + throw $this->newException( 18 + pht( 19 + 'Mailer cluster configuration is not valid: it should be a list '. 20 + 'of mailer configurations.')); 21 + } 22 + 23 + foreach ($value as $index => $spec) { 24 + if (!is_array($spec)) { 25 + throw $this->newException( 26 + pht( 27 + 'Mailer cluster configuration is not valid: each entry in the '. 28 + 'list must be a dictionary describing a mailer, but the value '. 29 + 'with index "%s" is not a dictionary.', 30 + $index)); 31 + } 32 + } 33 + 34 + $adapters = PhabricatorMailImplementationAdapter::getAllAdapters(); 35 + 36 + $map = array(); 37 + foreach ($value as $index => $spec) { 38 + try { 39 + PhutilTypeSpec::checkMap( 40 + $spec, 41 + array( 42 + 'key' => 'string', 43 + 'type' => 'string', 44 + 'priority' => 'optional int', 45 + 'options' => 'optional wild', 46 + )); 47 + } catch (Exception $ex) { 48 + throw $this->newException( 49 + pht( 50 + 'Mailer configuration has an invalid mailer specification '. 51 + '(at index "%s"): %s.', 52 + $index, 53 + $ex->getMessage())); 54 + } 55 + 56 + $key = $spec['key']; 57 + if (isset($map[$key])) { 58 + throw $this->newException( 59 + pht( 60 + 'Mailer configuration is invalid: multiple mailers have the same '. 61 + 'key ("%s"). Each mailer must have a unique key.', 62 + $key)); 63 + } 64 + $map[$key] = true; 65 + 66 + $priority = idx($spec, 'priority', 0); 67 + if ($priority <= 0) { 68 + throw $this->newException( 69 + pht( 70 + 'Mailer configuration ("%s") is invalid: priority must be '. 71 + 'greater than 0.', 72 + $key)); 73 + } 74 + 75 + $type = $spec['type']; 76 + if (!isset($adapters[$type])) { 77 + throw $this->newException( 78 + pht( 79 + 'Mailer configuration ("%s") is invalid: mailer type ("%s") is '. 80 + 'unknown. Supported mailer types are: %s.', 81 + $key, 82 + $type, 83 + implode(', ', array_keys($adapters)))); 84 + } 85 + 86 + $options = idx($spec, 'options', array()); 87 + try { 88 + id(clone $adapters[$type])->validateOptions($options); 89 + } catch (Exception $ex) { 90 + throw $this->newException( 91 + pht( 92 + 'Mailer configuration ("%s") specifies invalid options for '. 93 + 'mailer: %s', 94 + $key, 95 + $ex->getMessage())); 96 + } 97 + } 98 + } 99 + 100 + }