├── .vscode └── settings.json ├── CHANGELOG.md ├── psalm.xml.dist ├── LICENSE.md ├── .php_cs.dist.php ├── composer.json ├── README.md └── src └── MailpaceSwiftmailerTransport.php /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Mailpace", 4 | "Packagist", 5 | "htmlbody", 6 | "replyto", 7 | "textbody" 8 | ] 9 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `mailpace-swiftmailer` will be documented in this file. 4 | 5 | ## 1.0.0 - 2021-05-28 6 | 7 | - initial release 8 | 9 | # 1.0.3 - 2021-01-27 10 | 11 | - Move from OhMySMTP to MailPace 12 | 13 | # 1.0.4 - 2021-05-09 14 | 15 | - Move email-validator dependency to developer dependencies -------------------------------------------------------------------------------- /psalm.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) MailPace 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mailpace/mailpace-swiftmailer", 3 | "description": "The official Swiftmailer transport for MailPace", 4 | "keywords": [ 5 | "MailPace", 6 | "mailpace-swiftmailer", 7 | "swiftmailer", 8 | "transactional", 9 | "email", 10 | "transactional email" 11 | ], 12 | "homepage": "https://github.com/mailpace/mailpace-swiftmailer", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Paul", 17 | "email": "support@mailpace.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=7.3", 23 | "guzzlehttp/guzzle": ">=6.0", 24 | "swiftmailer/swiftmailer": ">=6.2.7" 25 | }, 26 | "require-dev": { 27 | "egulias/email-validator": "^2.1.10|^3.1", 28 | "friendsofphp/php-cs-fixer": "^2.17", 29 | "phpunit/phpunit": "^9.5", 30 | "spatie/ray": "^1.10", 31 | "vimeo/psalm": "^4.3" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Mailpace\\MailpaceSwiftmailer\\": "src" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Mailpace\\MailpaceSwiftmailer\\Tests\\": "tests" 41 | } 42 | }, 43 | "scripts": { 44 | "psalm": "vendor/bin/psalm", 45 | "test": "vendor/bin/phpunit", 46 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage", 47 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 48 | }, 49 | "config": { 50 | "sort-packages": true 51 | }, 52 | "minimum-stability": "dev", 53 | "prefer-stable": true 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The official Swiftmailer transport for MailPace 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/mailpace/mailpace-swiftmailer.svg?style=flat-square)](https://packagist.org/packages/mailpace/mailpace-swiftmailer) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/mailpace/mailpace-swiftmailer/Tests)](https://github.com/mailpace/mailpace-swiftmailer/actions?query=workflow%3ATests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/mailpace/mailpace-swiftmailer/Check%20&%20fix%20styling?label=code%20style)](https://github.com/mailpace/mailpace-swiftmailer/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/mailpace/mailpace-swiftmailer.svg?style=flat-square)](https://packagist.org/packages/mailpace/mailpace-swiftmailer) 7 | 8 | 9 | [MailPace](https://mailpace.com) lets you send transactional emails from your app over an easy to use API. 10 | 11 | This MailPace PHP Package is a transport for SwiftMailer to send emails via [MailPace](https://mailpace.com) to make sending emails from PHP apps super simple. You can use this with popular frameworks such as Laravel, Codeigniter and Symfony to send transactional emails, or with a standalone PHP app. 12 | 13 | This package uses the MailPace HTTPS [/send endpoint](https://docs.mailpace.com/reference/send) to send the email - which is generally faster and more reliable than SMTP, although you can of course use SMTP to send out emails from your PHP app without installing this package if you prefer. 14 | 15 | ## Pre-requisites 16 | 17 | You will need an MailPace account with a verified domain and organization with an active plan. 18 | 19 | ## Installation 20 | 21 | Install the package via composer: 22 | 23 | ```bash 24 | composer require mailpace/mailpace-swiftmailer 25 | ``` 26 | ### Account Setup 27 | 28 | Set up an account at [MailPace](https://app.mailpace.com/users/sign_up) and complete the Onboarding steps 29 | 30 | ### Configure the Package 31 | 32 | First you will need to retrieve your API token for your sending domain from [MailPace](https://app.mailpace.com). You can find it under Organization -> Domain -> API Tokens 33 | 34 | You'll need to store this API token somewhere in your app/runtime, we recommend Environment Variables for this, and the examples below assume that you have an environment variable called `OHMYSMTP_API_TOKEN` that contains your API token 35 | 36 | #### Sending without a framework 37 | 38 | ```php 39 | setFrom(['php@yourdomain.com' => 'Your Name']) 47 | ->setTo(['someone@example.com']) 48 | ->setBody('

HTML content

', 'text/html') 49 | ->addPart('Text Body','text/plain'); 50 | 51 | // Attachments 52 | $data = 'Raw Attachment Data'; 53 | $attachment = new Swift_Attachment($data, 'attachment.txt', 'text/plain'); 54 | $message->attach($attachment); 55 | 56 | // Email Tags 57 | $headers = $message->getHeaders(); 58 | $headers->addTextHeader('MailPace-Tag', 'tag-1'); 59 | $headers->addTextHeader('MailPace-Tag', 'tag with spaces'); 60 | 61 | $mailer->send($message); 62 | ?> 63 | ``` 64 | 65 | #### Sending with Laravel 66 | 67 | To send with Laravel you need to make a few small tweaks, but it really only takes a moment. 68 | 69 | 1. Add mailpace to the `config/mail.php` configuration file: 70 | 71 | ```php 72 | 'mailpace' => [ 73 | 'transport' => 'mailpace', 74 | ], 75 | ``` 76 | 77 | 78 | 2. Add the following to your `config/services.php` configuration file: 79 | 80 | ```php 81 | 'mailpace' => [ 82 | 'apiToken' => env('OHMYSMTP_API_TOKEN'), 83 | ] 84 | ``` 85 | 86 | 3. In `config/app.php`, add the following to the providers array: 87 | 88 | ```php 89 | App\Providers\MailpaceServiceProvider::class, 90 | ``` 91 | 92 | and remove / comment out the line: 93 | 94 | ```php 95 | Illuminate\Mail\MailServiceProvider::class, 96 | ``` 97 | 98 | 4. In your `.env` file (or wherever you store environment variables), change the `MAIL_MAILER` variable as follows: 99 | 100 | `MAIL_MAILER=mailpace` 101 | 102 | 5. Create a new file called `MailpaceServiceProvider.php` in `App/Providers` with the following contents: 103 | 104 | ```php 105 | app->singleton('mail.manager', function ($app) { 118 | $manager = new MailManager($app); 119 | $this->registerOhMySmtpTransport($manager); 120 | return $manager; 121 | }); 122 | } 123 | 124 | protected function registerOhMySmtpTransport(MailManager $manager) { 125 | $manager->extend('mailpace', function ($config) { 126 | if (! isset($config['apiToken'])) { 127 | $config = $this->app['config']->get('services.mailpace', []); 128 | } 129 | return new MailpaceSwiftmailerTransport($config['apiToken']); 130 | }); 131 | } 132 | } 133 | 134 | ``` 135 | After completing the above steps, all email will be sent via MailPace. 136 | 137 | ## Support 138 | 139 | For support please check the [MailPace Documentation](https://docs.mailpace.com) or contact us at support@mailpace.com 140 | 141 | ## Contributing 142 | 143 | Please ensure to add a test for any changes. To run the tests: 144 | 145 | `composer test` 146 | 147 | Pull requests always welcome 148 | 149 | ## License 150 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). -------------------------------------------------------------------------------- /src/MailpaceSwiftmailerTransport.php: -------------------------------------------------------------------------------- 1 | version = phpversion(); 38 | $this->os = PHP_OS; 39 | $this->_eventDispatcher = \Swift_DependencyContainer::getInstance()->lookup('transport.eventdispatcher'); 40 | $this->apiToken = $apiToken; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) 47 | { 48 | $client = $this->getHttpClient(); 49 | 50 | if ($sendEvent = $this->_eventDispatcher->createSendEvent($this, $message)) { 51 | $this->_eventDispatcher->dispatchEvent($sendEvent, 'beforeSendPerformed'); 52 | if ($sendEvent->bubbleCancelled()) { 53 | return 0; 54 | } 55 | } 56 | 57 | $php_version = $this->version; 58 | 59 | $response = $client->request('POST', 'https://app.mailpace.com/api/v1/send', [ 60 | 'json' => $this->convertToOms($message), 61 | 'headers' => [ 62 | 'MailPace-Server-Token' => $this->apiToken, 63 | 'Content-Type' => 'application/json', 64 | 'Accept' => 'application/json', 65 | 'User-Agent' => "MailPace Swiftmailer Package (PHP v$php_version)", 66 | ], 67 | 'http_errors' => false, // Errors are handled by Swiftmailer event listener 68 | ]); 69 | 70 | $success = $response->getStatusCode() === 200; 71 | 72 | if ($responseEvent = $this->_eventDispatcher->createResponseEvent($this, $response->getBody()->__toString(), $success)) { 73 | $this->_eventDispatcher->dispatchEvent($responseEvent, 'responseReceived'); 74 | } 75 | 76 | $sendEvent->setResult($success ? \Swift_Events_SendEvent::RESULT_SUCCESS : \Swift_Events_SendEvent::RESULT_FAILED); 77 | $this->_eventDispatcher->dispatchEvent($sendEvent, 'sendPerformed'); 78 | 79 | return $success ? $this->countSent($message) : 0; 80 | } 81 | 82 | /** 83 | * Get the number of delivered addresses 84 | * 85 | * @param Swift_Mime_SimpleMessage $message 86 | * @return int 87 | */ 88 | protected function countSent(Swift_Mime_SimpleMessage $message) 89 | { 90 | return count( 91 | array_merge( 92 | (array) $message->getTo(), 93 | (array) $message->getCc(), 94 | (array) $message->getBcc() 95 | ) 96 | ); 97 | } 98 | 99 | /** 100 | * Prep email by converting from emails from dict to array 101 | * 102 | * @param array $emails 103 | * @return array 104 | */ 105 | protected function prepareEmailAddresses($emails) 106 | { 107 | $convertedEmails = []; 108 | foreach ($emails as $email => $name) { 109 | $convertedEmails[] = $name ? $name . " <{$email}>" : $email; 110 | } 111 | 112 | return $convertedEmails; 113 | } 114 | 115 | /** 116 | * Extract the MIME type requested 117 | * 118 | * @param Swift_Mime_SimpleMessage $message 119 | * @param string $mimeType 120 | * @return Swift_Mime_MimePart|null 121 | */ 122 | protected function getMIMEPart(Swift_Mime_SimpleMessage $message, $mimeType) 123 | { 124 | foreach ($message->getChildren() as $part) { 125 | if (strpos($part->getContentType(), $mimeType) === 0 && ! ($part instanceof \Swift_Mime_Attachment)) { 126 | return $part; 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * Convert a Swift MIME Message to an MailPace API object 133 | * See https://docs.mailpace.com/reference/send for details 134 | * 135 | * @param Swift_Mime_SimpleMessage $message 136 | * @return array 137 | */ 138 | protected function convertToOms(Swift_Mime_SimpleMessage $message) 139 | { 140 | $payload = []; 141 | 142 | $this->addAddresses($payload, $message); 143 | $this->addSubject($payload, $message); 144 | $this->processMessageParts($payload, $message); 145 | if ($message->getHeaders()) { 146 | $this->processTagsFromHeaders($payload, $message); 147 | } 148 | 149 | return $payload; 150 | } 151 | 152 | /** 153 | * Add SwiftMailer recipients to MailPace payload 154 | * 155 | * @param array $payload 156 | * @param Swift_Mime_SimpleMessage $message 157 | */ 158 | protected function addAddresses(&$payload, $message) 159 | { 160 | $payload['from'] = join(',', $this->prepareEmailAddresses($message->getFrom())); 161 | if ($to = $message->getTo()) { 162 | $payload['to'] = join(',', $this->prepareEmailAddresses($to)); 163 | } 164 | if ($cc = $message->getCc()) { 165 | $payload['cc'] = join(',', $this->prepareEmailAddresses($cc)); 166 | } 167 | if ($bcc = $message->getBcc()) { 168 | $payload['bcc'] = join(',', $this->prepareEmailAddresses($bcc)); 169 | } 170 | if ($reply_to = $message->getReplyTo()) { 171 | /** 172 | * @psalm-suppress InvalidArgument 173 | */ 174 | $payload['replyto'] = join(',', $this->prepareEmailAddresses($reply_to)); 175 | } 176 | } 177 | 178 | /** 179 | * Add swiftmailer subject to MailPace payload 180 | * 181 | * @param array $payload 182 | * @param Swift_Mime_SimpleMessage $message 183 | */ 184 | protected function addSubject(&$payload, $message) 185 | { 186 | $payload['subject'] = $message->getSubject(); 187 | } 188 | 189 | /** 190 | * Turn SwiftMailer MIME parts into htmlbody, textbody and attachment array 191 | * 192 | * @param array $payload 193 | * @param Swift_Mime_SimpleMessage $message 194 | */ 195 | protected function processMessageParts(&$payload, $message) 196 | { 197 | switch ($message->getContentType()) { 198 | case 'text/html': 199 | case 'multipart/alternative': 200 | case 'multipart/mixed': 201 | $payload['htmlbody'] = $message->getBody(); 202 | 203 | break; 204 | default: 205 | $payload['textbody'] = $message->getBody(); 206 | 207 | break; 208 | } 209 | 210 | // If there are other html or text parts include them 211 | if ($plain = $this->getMIMEPart($message, 'text/plain')) { 212 | $payload['textbody'] = $plain->getBody(); 213 | } 214 | if ($html = $this->getMIMEPart($message, 'text/html')) { 215 | $payload['htmlbody'] = $html->getBody(); 216 | } 217 | 218 | // Attachments 219 | if ($message->getChildren()) { 220 | $payload['attachments'] = []; 221 | foreach ($message->getChildren() as $attachment) { 222 | if (is_object($attachment) and $attachment instanceof \Swift_Mime_Attachment) { 223 | $attachments = [ 224 | 'name' => $attachment->getFilename(), 225 | 'content' => base64_encode($attachment->getBody()), 226 | 'content_type' => $attachment->getContentType(), 227 | ]; 228 | if ($attachment->getDisposition() != 'attachment' && $attachment->getId() != null) { 229 | $attachments['cid'] = 'cid:' . $attachment->getId(); 230 | } 231 | $payload['attachments'][] = $attachments; 232 | } 233 | } 234 | } 235 | } 236 | 237 | /** 238 | * Move MailPace-Tags from headers into the API payload 239 | * 240 | * @param array $payload 241 | * @param Swift_Mime_SimpleMessage $message 242 | */ 243 | protected function processTagsFromHeaders(&$payload, $message) 244 | { 245 | $payload['tags'] = []; 246 | foreach ($message->getHeaders()->getAll() as $value) { 247 | $fieldName = $value->getFieldName(); 248 | if ($fieldName == 'MailPace-Tag') { 249 | array_push($payload['tags'], $value->getValue()); 250 | } 251 | } 252 | } 253 | 254 | /** 255 | * {@inheritdoc} 256 | */ 257 | public function registerPlugin(Swift_Events_EventListener $plugin) 258 | { 259 | $this->_eventDispatcher->bindEventListener($plugin); 260 | } 261 | 262 | /** 263 | * Get Guzzle HTTP client instance 264 | * 265 | * @return \GuzzleHttp\Client 266 | */ 267 | protected function getHttpClient() 268 | { 269 | return new Client; 270 | } 271 | 272 | /** 273 | * {@inheritdoc} 274 | */ 275 | public function isStarted() 276 | { 277 | return true; 278 | } 279 | 280 | /** 281 | * {@inheritdoc} 282 | */ 283 | public function start() 284 | { 285 | return true; 286 | } 287 | 288 | /** 289 | * {@inheritdoc} 290 | */ 291 | public function stop() 292 | { 293 | return true; 294 | } 295 | 296 | /** 297 | * Ping 298 | * 299 | * @return bool 300 | */ 301 | public function ping() 302 | { 303 | return true; 304 | } 305 | } 306 | --------------------------------------------------------------------------------