├── .circleci
└── config.yml
├── .gitignore
├── .php_cs.dist
├── .travis.yml
├── Dockerfile.dev
├── LICENCE
├── README.md
├── composer.json
├── docker-compose.yml
├── docker-compose
├── mysql
│ ├── init
│ │ └── 01-databases.sql
│ └── my.cnf
└── nginx
│ └── default.conf
├── phpunit.xml
├── publishable
└── config
│ └── multimail.php
├── src
├── DatabaseConfigMailSettings.php
├── Exceptions
│ ├── EmailNotInConfigException.php
│ ├── InvalidConfigKeyException.php
│ ├── NoDefaultException.php
│ └── NotInitializedException.php
├── Facades
│ └── MultiMail.php
├── FileConfigMailSettings.php
├── Jobs
│ └── SendMailJob.php
├── MailSettings.php
├── Migrations
│ ├── 2021_04_10_141537_create_email_providers_table.php
│ └── 2021_04_10_141626_create_email_accounts_table.php
├── Models
│ ├── EmailAccount.php
│ └── EmailProvider.php
├── MultiMailServiceProvider.php
├── MultiMailer.php
├── PendingMail.php
└── TransportManager.php
└── tests
├── .env.example
├── Fixtures
└── view.blade.php
├── Integration
├── MultiMailDatabaseTest.php
├── MultiMailTest.php
├── SMPTTest.php
└── TestMail.php
├── TestCase.php
├── Traits
└── MailTrap.php
└── Unit
├── ConfigTest.php
├── MultiMailTest.php
└── PendingMailTest.php
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # PHP CircleCI 2.0 configuration file
2 | # See: https://circleci.com/docs/2.0/language-php/
3 | version: 2.1
4 |
5 | orbs:
6 | codecov: codecov/codecov@3.2.2
7 |
8 | # Define a job to be invoked later in a workflow.
9 | # See: https://circleci.com/docs/2.0/configuration-reference/#jobs
10 | jobs:
11 | build:
12 | # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
13 | # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
14 | docker:
15 | # Specify the version you desire here
16 | - image: circleci/php:8.0-node-browsers
17 |
18 | # Specify service dependencies here if necessary
19 | # CircleCI maintains a library of pre-built images
20 | # documented at https://circleci.com/docs/2.0/circleci-images/
21 | # Using the RAM variation mitigates I/O contention
22 | # for database intensive operations.
23 | # - image: circleci/mysql:5.7-ram
24 | #
25 | # - image: redis:2.8.19
26 |
27 | # Add steps to the job
28 | # See: https://circleci.com/docs/2.0/configuration-reference/#steps
29 | steps:
30 | - checkout
31 |
32 | - run: sudo apt update # PHP CircleCI 2.0 Configuration File# PHP CircleCI 2.0 Configuration File sudo apt install zlib1g-dev libsqlite3-dev
33 | - run: sudo docker-php-ext-install zip
34 |
35 | # Download and cache dependencies
36 | - restore_cache:
37 | keys:
38 | # "composer.lock" can be used if it is committed to the repo
39 | - v1-dependencies-{{ checksum "composer.json" }}
40 | # fallback to using the latest cache if no exact match is found
41 | - v1-dependencies-
42 |
43 | - run: composer install -n --prefer-dist
44 |
45 | - save_cache:
46 | key: v1-dependencies-{{ checksum "composer.json" }}
47 | paths:
48 | - ./vendor
49 |
50 | # run tests with phpunit or codecept
51 | #- run: ./vendor/bin/phpunit
52 |
53 | - run:
54 | name: "Run tests"
55 | command: phpdbg -qrr vendor/bin/phpunit --coverage-html build/coverage-report --coverage-clover coverage.xml
56 | - store_artifacts:
57 | path: build/coverage-report
58 | - codecov/upload
59 |
60 |
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | .lock
3 | composer.lock
4 | .phpunit.result.cache
5 | tests/_report
6 | .env
7 | .idea
8 | /html
9 |
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | setRules([
9 | '@PSR2' => true,
10 | 'blank_line_after_namespace' => true,
11 | 'class_definition' => true,
12 | 'elseif' => true,
13 | 'encoding' => true,
14 | 'full_opening_tag' => true,
15 | 'function_declaration' => true,
16 | 'indentation_type' => true,
17 | 'lowercase_constants' => true,
18 | 'lowercase_keywords' => true,
19 | 'method_argument_space' => true,
20 | 'no_break_comment' => true,
21 | 'no_closing_tag' => true,
22 | 'no_spaces_after_function_name' => true,
23 | 'no_trailing_whitespace' => true,
24 | 'no_trailing_whitespace_in_comment' => true,
25 | 'no_extra_blank_lines' => true,
26 | 'align_multiline_comment' => true,
27 | 'array_syntax' => ['syntax' => 'short'],
28 | 'array_indentation' => true,
29 | //'binary_operator_spaces' => true, ('=' is binary operator..)
30 | 'blank_line_after_opening_tag' => true,
31 | 'cast_spaces' => true,
32 | 'combine_consecutive_issets' => true,
33 | 'combine_consecutive_unsets' => true,
34 | 'concat_space' => ['spacing' => 'one'],
35 | 'explicit_indirect_variable' => true,
36 | 'fully_qualified_strict_types' => true,
37 | 'function_typehint_space' => true,
38 | 'linebreak_after_opening_tag' => true,
39 | 'lowercase_static_reference' => true,
40 | 'no_blank_lines_after_class_opening' => true,
41 | 'no_empty_comment' => true,
42 | 'no_empty_phpdoc' => true,
43 | 'no_empty_statement' => true,
44 | 'no_multiline_whitespace_around_double_arrow' => true,
45 | 'no_null_property_initialization' => true,
46 | 'no_trailing_comma_in_singleline_array' => true,
47 | 'no_useless_return' => true,
48 | 'no_useless_else' => true,
49 | 'no_unused_imports' => true,
50 | 'ordered_imports' => true,
51 | 'ordered_class_elements' => true,
52 | 'single_quote' => true,
53 | 'whitespace_after_comma_in_array' => true,
54 | 'backtick_to_shell_exec' => true,
55 | 'class_attributes_separation' => true,
56 | 'no_superfluous_elseif' => true,
57 | 'ternary_operator_spaces' => true,
58 | 'ternary_to_null_coalescing' => true,
59 | 'trailing_comma_in_multiline_array' => true,
60 | 'trim_array_spaces' => true,
61 | 'space_after_semicolon' => true,
62 | ])
63 | ->setFinder(PhpCsFixer\Finder::create()
64 | ->exclude('vendor')
65 | ->in(__DIR__)
66 | )
67 | ;
68 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.2
5 | - 7.3
6 | - 7.4
7 |
8 | before_script:
9 | - composer self-update
10 | - composer install --prefer-dist --no-interaction
11 |
12 | script: ./vendor/bin/phpunit --coverage-clover clover.xml
13 |
14 | after_success:
15 | - bash <(curl -s https://codecov.io/bash)
16 |
17 | # Only runs tests on master or PR to master branch
18 | branches:
19 | only:
20 | - "master"
21 |
22 |
--------------------------------------------------------------------------------
/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM php:7.2-fpm
2 |
3 | # Arguments defined in docker-compose.yml
4 | ARG user
5 | ARG uid
6 |
7 | # Install system dependencies
8 | RUN apt-get update && apt-get install -y \
9 | git \
10 | curl \
11 | libpng-dev \
12 | libonig-dev \
13 | libxml2-dev \
14 | zip \
15 | unzip \
16 | libzip-dev \
17 | -y mariadb-client
18 |
19 | # Install and enable xdebug (for generating PHPUnit Test Coverage Report)
20 | #
21 | # ADVICE: Enabling XDebug renders PHP script execution slower
22 | #
23 | RUN pecl install xdebug-2.9.8 \
24 | && docker-php-ext-enable xdebug
25 |
26 | # Clear cache
27 | RUN apt-get clean && rm -rf /var/lib/apt/lists/*
28 |
29 | # Install PHP extensions
30 | RUN docker-php-ext-install zip mysqli pdo_mysql mbstring exif pcntl bcmath gd && docker-php-ext-enable mysqli
31 |
32 | COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
33 |
34 | # Create system user to run Composer and Artisan Commands
35 | RUN useradd -G www-data,root -u $uid -d /home/$user $user
36 | RUN mkdir -p /home/$user/.composer && \
37 | chown -R $user:$user /home/$user
38 |
39 | # Set working directory
40 | WORKDIR /var/www
41 |
42 | USER $user
43 |
44 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Dr. Adam Nielsen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Multimail
2 |
3 | 
4 |
5 | [](https://circleci.com/gh/iwasherefirst2/laravel-multimail/tree/master)
6 | [](https://codecov.io/gh/iwasherefirst2/laravel-multimail)
7 |
8 | This package helps you to send mails from your Laravel application from multiple email accounts.
9 |
10 | The package supports sending queued, localized and bulk mails.
11 |
12 | This package works for `SMTP` and `log` drivers.
13 |
14 | ## Table of Contents
15 |
16 | - [Requirments](#requirements)
17 | - [Installation](#installation)
18 | - [Usage](#usage)
19 | - [Basic Examples](#basic-examples)
20 | - [Queued Mails](#queued-mails)
21 | - [Specify in Mailable](#specify-in-mailable)
22 | - [Bulk messages](#bulk-messages)
23 | - [Special Settings](#special-settings)
24 | - [Multiple Mail Providers](#multiple-mail-providers)
25 | - [Default mailaccount](#default-mailaccount)
26 | - [Testing](#testing)
27 | - [Get Mail From Database](#get-mail-from-database)
28 | - [Troubleshoot](#troubleshoot)
29 | - [For Package Developer](#for-package-developer)
30 |
31 | ## Requirements
32 |
33 | Laravel 5, 6,7 or 8.
34 | Larave 9 and 10 are not supported.
35 |
36 | ## Installation
37 |
38 | Install the package into your Laraval application with composer:
39 |
40 | composer require iwasherefirst2/laravel-multimail
41 |
42 | Publish the config file:
43 |
44 | php artisan vendor:publish --provider="IWasHereFirst2\LaravelMultiMail\MultiMailServiceProvider" --tag=config
45 |
46 | Configure your email clients in `config/multimail.php`:
47 |
48 | 'emails' => [
49 | 'office@example.net' =>
50 | [
51 | 'pass' => env('first_mail_password'),
52 | 'from_name' => "Max Musterman",
53 | ],
54 | 'contact@example.net' =>
55 | [
56 | 'pass' => env('second_mail_password')
57 | ],
58 | ],
59 |
60 | Make sure to put your credentials in the `.env` file, so they don't get tracked in your repository.
61 |
62 | For each mail you may specify multiple columns:
63 |
64 | Attribut | Functionality | required
65 | --- | --- | ---
66 | `pass` | Password of email account | yes
67 | `username` | Username of email account, only neccessary if different from email address | no
68 | `from_name` | Name that should appear in front of email | no
69 | `provider` | Provider of email account, only necessary if mail host/encription/port is not default (see [here](#multiple-mail-providers) for more) | no
70 |
71 | ## Usage
72 |
73 | One may send a mail using `\MultiMail` instead of `\Mail`. The methods `to`, `cc`, `bcc`, `locale` are exactly the same as provided by the [mail facade](https://laravel.com/docs/5.8/mail#sending-mail) (note that `locale` is only available since Laravel 5.6).
74 |
75 | The `from` method from `MultiMail` needs a string of an email provided in `config/multimail.php`. You can pass optionaly a second parameter as from name instetad of using the default falue given in the config.
76 | When using `send` or `queue` the mail will be send from the mail account specified in `cofing/multimail.php`.
77 |
78 | ### Basic Examples
79 |
80 | This example assumes that `office@example.net` and `contact@example.net` have been specified in `config/multimail.php`.
81 |
82 | // Send mail from office@example.net
83 | \MultiMail::to($user)->from('office@example.com')->send(new \App\Mail\Invitation($user));
84 |
85 | // Send from malaccount email@gmail.com
86 | \MultiMail::to($user)->from('email@example.net')->locale('en')->send(new \App\Mail\Invitation($user));
87 |
88 | ### Queued Mails
89 |
90 | Queued mails work exactly the same as for the normal [Mail](https://laravel.com/docs/5.8/mail#queueing-mail) facade,
91 | i.e. they are either send explicitly be the `queue` method or the mailable class implements the `ShouldQueue` contract.
92 |
93 | // Queue Mail
94 | \MultiMail::from('contact@foo.org')->queue(new \App\Mail\Invitation($user));
95 |
96 | It is of course necessary to install a [queue driver](https://laravel.com/docs/5.8/queues#driver-prerequisites).
97 |
98 | ### Specify in mailable
99 |
100 | You may set `to`, `cc`, `bcc`, `locale` and `from` in your mailable class. In this case, you could reduce the basic example from above to:
101 |
102 | // Send mail from office@example.net
103 | \MultiMail::send(new \App\Mail\Invitation($user));
104 |
105 | Mailable:
106 |
107 | /**
108 | * Create a new message instance.
109 | *
110 | * @return void
111 | */
112 | public function __construct($user)
113 | {
114 | $this->to = $user;
115 | $this->fromMailer = 'office@example.com'
116 | $this->locale('en');
117 | }
118 |
119 | /**
120 | * Build the message.
121 | *
122 | * @return $this
123 | */
124 | public function build()
125 | {
126 | return $this->markdown('emails.invitation')
127 | ->subject('Invitation mail');
128 | }
129 |
130 |
131 |
132 | ### Bulk messages
133 |
134 | For bulk messages, you may first require a mailer object. You can define a pause in seconds ($timeout) after a number of mails ($frequency) has been send.
135 |
136 | $mailer = \MultiMail::getMailer('office@example.com' , $timeout, $frequency);
137 |
138 | Then you can iterate through your list.
139 |
140 | foreach($users as $user){
141 | $mailer->send(new \App\Mail\Invitation($user));
142 | };
143 |
144 |
145 | ## Special Settings
146 |
147 | ### Multiple Mail Providers
148 |
149 | If you wish to send from mails with different provider, then you may create another provider in the `provider` array and reference it inside the `emails` array:
150 |
151 |
152 | 'emails' => [
153 | 'office@example.net' =>
154 | [
155 | 'pass' => env('first_mail_password'),
156 | 'username' => env('first_mail_username'),
157 | 'from_name' => "Max Musterman",
158 | // <------ no provider given because 'default' provider is used
159 | ],
160 | 'contact@other_domain.net' =>
161 | [
162 | 'pass' => env('second_mail_password'),
163 | 'username' => env('second_mail_username'),
164 | 'from_name' => "Alice Armania",
165 | 'provider' => 'new_provider', // <------ specify new provider here
166 | ],
167 | ],
168 |
169 | 'provider' => [
170 | 'default' =>
171 | [
172 | 'host' => env('MAIL_HOST'),
173 | 'port' => env('MAIL_PORT'),
174 | 'encryption' => env('MAIL_ENCRYPTION'),
175 | 'driver' => env('MAIL_DRIVER'),
176 | ],
177 | 'new_provider' =>
178 | [
179 | 'host' => env('MAIL_HOST_PROVIDER_B'),
180 | 'port' => env('MAIL_PORT_PROVIDER_B'),
181 | 'encryption' => env('MAIL_ENCRYPTION_PROVIDER_B'),
182 | 'driver' => env('MAIL_DRIVER_B'),
183 | // you may also add options like `stream`, `source_ip` and `local_domain`
184 | ]'
185 | ],
186 |
187 |
188 |
189 | ### Default mailaccount
190 |
191 | You may provide `default` credentials inside the `email` array from `config/multimail.php`:
192 |
193 | 'emails' => [
194 | 'office@example.net' =>
195 | [
196 | 'pass' => env('first_mail_password'),
197 | 'username' => env('first_mail_username'),
198 | 'from_name' => "Max Musterman",
199 | ],
200 | 'contact@example.net' =>
201 | [
202 | 'pass' => env('second_mail_password'),
203 | 'username' => env('second_mail_username'),
204 | 'from_name' => "Alice Armania",
205 | ],
206 | 'default' =>
207 | [
208 | 'pass' => env('MAIL_PASSWORD'),
209 | 'username' => env('MAIL_USERNAME'),
210 | ]
211 | ],
212 |
213 | When `first_mail_password` and `first_mail_username` are empty, `office@example.net` will use credentials specified by `default`. This is useful for your local development, when you want to send all mails from one mailaccount while testing. This way you only need to specify `MAIL_PASSWORD` and `MAIL_USERNAME` locally.
214 |
215 | ## Testing
216 |
217 | #### Don't put credentials in local `env`
218 |
219 | Do not specify any email accounts in your local `.env`. Otherwise you may risk to send testing mails to actual users.
220 |
221 | #### Use one fake mail account or log
222 |
223 | Use `log` driver or setup a fake mail SMTP account like [mailtrap](https://mailtrap.io/) or similar services.
224 |
225 | It is not needed to specify the same credentials for all your email accounts. Instead, simply provide a default mail account (see above `Default mail account`).
226 |
227 | #### Use log mail driver on testing
228 |
229 | To avoid latency, I recommend to always use the `log` mail driver when `phpunit` is running. You can set the mail driver in your `phpunit.xml` file like this: ``.
230 |
231 | #### Use Mocking
232 |
233 | If you want to use the mocking feature [Mail fake](https://laravel.com/docs/mocking#mail-fake) during your tests, enable `use_default_mail_facade_in_tests`
234 | in your config file `config/multimail.php`. Note that `assertQueued` will never be true, because `queued` mails are actually send through `sent` through a job.
235 |
236 | ### Get Mail From Database
237 |
238 | If you want to load your mail account configuration from database
239 | publish the package migrations:
240 |
241 | php artisan vendor:publish --provider="IWasHereFirst2\LaravelMultiMail\MultiMailServiceProvider" --tag=migrations
242 |
243 | In your migration folder are now two tabels, email_accounts and email_providers
244 |
245 | Instead of adding emails to the config they should be added to the table email_accounts.
246 |
247 | Make sure to update your config `config/multimail.php`:
248 |
249 | \IWasHereFirst2\LaravelMultiMail\DatabaseConfigMailSettings::class,
254 |
255 | //...
256 | ];
257 |
258 | The emails will now be read from the database instead from the configuration file.
259 | If no provider is provided in email_accounts (column provider is nullable),
260 | then the default profider from `config/multimail.php` will be considerd.
261 |
262 | If you want to make customizations, copy the class `\IWasHereFirst2\LaravelMultiMail\DatabaseConfigMailSettings`
263 | somewhere in your application, adjust the namespace, and update the reference `mail_settings_class` in your config file.
264 |
265 | ## Troubleshoot
266 |
267 | #### Laravel 7 is not working
268 |
269 | Please update to version 1.2.2 to support Laravel 7
270 |
271 | ## For Package Developer
272 |
273 | If you plan to contribute to this package, please make sure that the unit tests aswell as the integration tests
274 | all succeed. In order to test the integration tests please create a free mailtraip account, copy `tests/.env.example`
275 | to `tests/.env` and add your mailtrap API credentials in `tests/.env`. The integration tests will now send
276 | a test email to your mailtrap account and verify through the API if the mail was successfully send.
277 |
278 | The package ships with a Dockerfile to make it easy to run the tests for you. Simply follow these steps:
279 |
280 | docker-compose up --build
281 | docker-compose exec app bash
282 | composer install
283 | ./vendor/bin/phpunit
284 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iwasherefirst2/laravel-multimail",
3 | "description": "A package to send mails easily from multiple mail accounts with Laravel",
4 | "keywords": ["multiple providers", "laravel", "emails"],
5 | "license": "MIT",
6 | "homepage": "https://github.com/iwasherefirst2/Laravel-MultiMail",
7 | "authors": [
8 | {
9 | "name": "Dr. Adam Nielsen",
10 | "email": "info@drnielsen.de"
11 | }
12 | ],
13 | "require": {
14 | "laravel/framework": "^5.0|^6.0|^7.0|^8.0"
15 | },
16 | "require-dev": {
17 | "orchestra/testbench": "^4.0",
18 | "guzzlehttp/guzzle": "^6.4"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "IWasHereFirst2\\LaravelMultiMail\\": "src/"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "IWasHereFirst2\\LaravelMultiMail\\Tests\\": "tests/"
28 | }
29 | },
30 | "minimum-stability": "stable",
31 | "extra": {
32 | "laravel": {
33 | "providers": [
34 | "IWasHereFirst2\\LaravelMultiMail\\MultiMailServiceProvider"
35 | ],
36 | "aliases":{
37 | "MultiMail": "IWasHereFirst2\\LaravelMultiMail\\Facades\\MultiMail"
38 | }
39 | }
40 | },
41 | "scripts": {
42 | "coverage-report": [
43 | "vendor/bin/phpunit -d xdebug.profiler_enable=On --coverage-html tests/_report/"
44 | ]
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 | services:
3 | app:
4 | build:
5 | args:
6 | user: sammy
7 | uid: 1000
8 | context: ./
9 | dockerfile: Dockerfile.dev
10 | image: mailable
11 | container_name: mailable-app
12 | working_dir: /var/www/
13 | environment:
14 | - COMPOSER_MEMORY_LIMIT=-1
15 | depends_on:
16 | - db
17 | volumes:
18 | - ./:/var/www
19 | networks:
20 | - lahmi
21 |
22 | db:
23 | image: mysql:5.7
24 | container_name: mailable-db
25 | environment:
26 | MYSQL_DATABASE: ${DB_DATABASE}
27 | MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
28 | MYSQL_PASSWORD: ${DB_PASSWORD}
29 | MYSQL_USER: ${DB_USERNAME}
30 | SERVICE_TAGS: dev
31 | SERVICE_NAME: mysql
32 | MYSQL_ROOT_PASSWORD: local
33 | volumes:
34 | - dbdata:/var/lib/mysql
35 | - ./docker-compose/mysql/my.cnf:/etc/mysql/my.cnf
36 | - ./docker-compose/mysql/init:/docker-entrypoint-initdb.d
37 | ports:
38 | - 3308:3306
39 | networks:
40 | - lahmi
41 |
42 | nginx:
43 | image: nginx:alpine
44 | container_name: mailable-nginx
45 | ports:
46 | - 8006:80
47 | depends_on:
48 | - db
49 | - app
50 | volumes:
51 | - ./:/var/www
52 | - ./docker-compose/nginx:/etc/nginx/conf.d/
53 | networks:
54 | - lahmi
55 |
56 | networks:
57 | lahmi:
58 | driver: bridge
59 |
60 | volumes:
61 | dbdata:
62 | driver: local
63 |
64 |
--------------------------------------------------------------------------------
/docker-compose/mysql/init/01-databases.sql:
--------------------------------------------------------------------------------
1 | # create databases
2 | CREATE DATABASE IF NOT EXISTS `local_laravel`;
3 |
4 | # create local_developer user and grant rights
5 | CREATE USER 'local_developer'@'db' IDENTIFIED BY 'secret';
6 | GRANT ALL PRIVILEGES ON *.* TO 'local_developer'@'%';
7 |
8 |
--------------------------------------------------------------------------------
/docker-compose/mysql/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | general_log = 1
3 | general_log_file = /var/lib/mysql/general.log
4 |
5 |
--------------------------------------------------------------------------------
/docker-compose/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | index index.php index.html;
4 | error_log /var/log/nginx/error.log;
5 | access_log /var/log/nginx/access.log;
6 | root /var/www/public;
7 | location ~ \.php$ {
8 | try_files $uri =404;
9 | fastcgi_split_path_info ^(.+\.php)(/.+)$;
10 | fastcgi_pass app:9000;
11 | fastcgi_index index.php;
12 | include fastcgi_params;
13 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
14 | fastcgi_param PATH_INFO $fastcgi_path_info;
15 | }
16 | location / {
17 | try_files $uri $uri/ /index.php?$query_string;
18 | gzip_static on;
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/Unit
16 |
17 |
18 |
19 | ./tests/Integration
20 |
21 |
22 |
23 |
24 | ./src
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/publishable/config/multimail.php:
--------------------------------------------------------------------------------
1 | true,
13 |
14 | 'emails' => [
15 | 'office@example.com' => [
16 | 'pass' => env('MAIL_PASSWORD'),
17 | 'username' => env('MAIL_USERNAME'),
18 | 'from_name' => 'Max Musterman',
19 | 'reply_to_mail' => 'reply@example.com',
20 | ],
21 | 'contact@example.net' => [
22 | 'pass' => env('second_mail_password'),
23 | ],
24 | ],
25 |
26 | 'provider' => [
27 | 'default' => [
28 | 'host' => env('MAIL_HOST'),
29 | 'port' => env('MAIL_PORT'),
30 | 'encryption' => env('MAIL_ENCRYPTION'),
31 | ],
32 | ],
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/src/DatabaseConfigMailSettings.php:
--------------------------------------------------------------------------------
1 | parseEmail($key);
43 |
44 | try {
45 | $this->account = EmailAccount::where('email', '=', $this->email)->firstOrFail();
46 | } catch (\Exception $e) {
47 | throw new Exceptions\EmailNotInConfigException($this->email);
48 | }
49 |
50 | if (empty($this->name)) {
51 | $this->name = $this->account->from_name;
52 | }
53 |
54 | $this->loadProvider();
55 |
56 | // If credentials are empty, load default values.
57 | // This makes local testing for many emails
58 | // very convenient.
59 | if ($this->isEmpty()) {
60 | $this->loadDefault();
61 | }
62 |
63 | return $this;
64 | }
65 | /**
66 | * Check if log driver is used.
67 | *
68 | * @return boolean
69 | */
70 | public function isLogDriver()
71 | {
72 | return (isset($this->provider['driver']) && $this->provider['driver'] == 'log');
73 | }
74 |
75 | /**
76 | * Get provider.
77 | *
78 | * @return array
79 | */
80 | public function getProvider()
81 | {
82 | return $this->provider;
83 | }
84 |
85 | /**
86 | * Get setting.
87 | *
88 | * @return array
89 | */
90 | public function getSetting()
91 | {
92 | return $this->account->toArray();
93 | }
94 |
95 | /**
96 | * Return email of sender.
97 | *
98 | * @return string
99 | */
100 | public function getFromEmail()
101 | {
102 | return $this->account->from_mail ?? $this->email;
103 | }
104 |
105 | /**
106 | * Return name of sender.
107 | *
108 | * @return string
109 | */
110 | public function getFromName()
111 | {
112 | return $this->name;
113 | }
114 |
115 | /**
116 | * Return email of sender.
117 | *
118 | * @return string
119 | */
120 | public function getReplyEmail()
121 | {
122 | return $this->account->reply_to_mail;
123 | }
124 |
125 | /**
126 | * Return name of sender.
127 | *
128 | * @return string
129 | */
130 | public function getReplyName()
131 | {
132 | return $this->account->reply_to_name;
133 | }
134 |
135 | public function getEmail()
136 | {
137 | return $this->email;
138 | }
139 |
140 | /**
141 | * Check if email, pass and username are not empty
142 | *
143 | * @return boolean
144 | */
145 | private function isEmpty()
146 | {
147 | return (empty($this->email) || empty($this->account) || empty($this->account->pass) || empty($this->account->username));
148 | }
149 |
150 | /**
151 | * Load default setting. If default setting is
152 | * invalid throw exception
153 | *
154 | * @return void
155 | */
156 | private function loadDefault()
157 | {
158 | $this->account = (object) config('multimail.emails.default');
159 |
160 | $this->loadProvider();
161 |
162 | if ((!isset($this->provider['driver']) || $this->provider['driver'] != 'log') && (empty($this->acount->pass) || empty($this->account->username))) {
163 | throw new Exceptions\NoDefaultException($this->email);
164 | }
165 | }
166 |
167 | /**
168 | * Parse $key into email and possible from name
169 | *
170 | * @param mixed string/array
171 | * @return void
172 | */
173 | private function parseEmail($key)
174 | {
175 | if (!is_array($key)) {
176 | $this->email = $key;
177 | return;
178 | }
179 |
180 | $this->name = $key['name'] ?? null;
181 |
182 | if (empty($key['email'])) {
183 | throw new Exceptions\InvalidConfigKeyException;
184 | }
185 |
186 | $this->email = $key['email'];
187 | }
188 |
189 |
190 | private function loadProvider()
191 | {
192 |
193 | if (!empty($this->account->provider)) {
194 | $this->provider = $this->account->provider->toArray();
195 | }
196 |
197 | if (empty($this->provider)) {
198 | $this->provider = config('multimail.provider.default');
199 | }
200 | }
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/src/Exceptions/EmailNotInConfigException.php:
--------------------------------------------------------------------------------
1 | message = 'Email ' . $mail . ' not found in config/multimail.php!';
10 | parent::__construct();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidConfigKeyException.php:
--------------------------------------------------------------------------------
1 | message = "Mailer name has to be provided in array as column 'email'";
10 | parent::__construct();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exceptions/NoDefaultException.php:
--------------------------------------------------------------------------------
1 | message = 'Username or password for ' . $mail . ' is missing in config/multimail.php and no default specified!';
10 | parent::__construct();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Exceptions/NotInitializedException.php:
--------------------------------------------------------------------------------
1 | message = 'Please call loadConfiguration($key) before anything else.';
10 | parent::__construct();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Facades/MultiMail.php:
--------------------------------------------------------------------------------
1 | parseEmail($key);
43 |
44 | try {
45 | $this->settings = config('multimail.emails')[$this->email];
46 | } catch (\Exception $e) {
47 | throw new Exceptions\EmailNotInConfigException($this->email);
48 | }
49 |
50 | if (empty($this->name)) {
51 | $this->name = $this->settings['from_name'] ?? null;
52 | }
53 |
54 | $this->loadProvider();
55 |
56 | // If credentials are empty, load default values.
57 | // This makes local testing for many emails
58 | // very convenient.
59 | if ($this->isEmpty()) {
60 | $this->loadDefault();
61 | }
62 |
63 | return $this;
64 | }
65 | /**
66 | * Check if log driver is used.
67 | *
68 | * @return boolean
69 | */
70 | public function isLogDriver()
71 | {
72 | return (isset($this->provider['driver']) && $this->provider['driver'] == 'log');
73 | }
74 |
75 | /**
76 | * Get provider.
77 | *
78 | * @return array
79 | */
80 | public function getProvider()
81 | {
82 | return $this->provider;
83 | }
84 |
85 | /**
86 | * Get setting.
87 | *
88 | * @return array
89 | */
90 | public function getSetting()
91 | {
92 | return $this->settings;
93 | }
94 |
95 | /**
96 | * Return email of sender.
97 | *
98 | * @return string
99 | */
100 | public function getFromEmail()
101 | {
102 | return $this->settings['from_mail'] ?? $this->email;
103 | }
104 |
105 | /**
106 | * Return name of sender.
107 | *
108 | * @return string
109 | */
110 | public function getFromName()
111 | {
112 | return $this->name;
113 | }
114 |
115 | /**
116 | * Return email of sender.
117 | *
118 | * @return string
119 | */
120 | public function getReplyEmail()
121 | {
122 | return $this->settings['reply_to_mail'] ?? null;
123 | }
124 |
125 | /**
126 | * Return name of sender.
127 | *
128 | * @return string
129 | */
130 | public function getReplyName()
131 | {
132 | return $this->settings['reply_to_name'] ?? null;
133 | }
134 |
135 | public function getEmail()
136 | {
137 | return $this->email;
138 | }
139 |
140 | /**
141 | * Check if email, pass and username are not empty
142 | *
143 | * @return boolean
144 | */
145 | private function isEmpty()
146 | {
147 | return (empty($this->email) || empty($this->settings) || empty($this->settings['pass']) || empty($this->settings['username']));
148 | }
149 |
150 | /**
151 | * Load default setting. If default setting is
152 | * invalid throw exception
153 | *
154 | * @return void
155 | */
156 | private function loadDefault()
157 | {
158 | $this->settings = config('multimail.emails.default');
159 |
160 | $this->loadProvider();
161 |
162 | if ((!isset($this->provider['driver']) || $this->provider['driver'] != 'log') && (empty($this->settings['pass']) || empty($this->settings['username']))) {
163 | throw new Exceptions\NoDefaultException($this->email);
164 | }
165 | }
166 |
167 | /**
168 | * Parse $key into email and possible from name
169 | *
170 | * @param mixed string/array
171 | * @return void
172 | */
173 | private function parseEmail($key)
174 | {
175 | if (!is_array($key)) {
176 | $this->email = $key;
177 | return;
178 | }
179 |
180 | $this->name = $key['name'] ?? null;
181 |
182 | if (empty($key['email'])) {
183 | throw new Exceptions\InvalidConfigKeyException;
184 | }
185 |
186 | $this->email = $key['email'];
187 | }
188 |
189 |
190 | private function loadProvider()
191 | {
192 | if (!empty($this->settings['provider'])) {
193 | $this->provider = config('multimail.provider.' . $this->settings['provider']);
194 | }
195 |
196 | if (empty($this->provider)) {
197 | $this->provider = config('multimail.provider.default');
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/Jobs/SendMailJob.php:
--------------------------------------------------------------------------------
1 | mailer_name = $mailer_name;
28 | $this->mailable = $mailable;
29 | $this->fromName = $fromName;
30 | }
31 |
32 | /**
33 | * Send Email out. This is a queue workaround
34 | *
35 | * @return void
36 | */
37 | public function handle()
38 | {
39 | MultiMail::sendMail($this->mailable, $this->mailer_name, $this->fromName);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/MailSettings.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('host');
19 | $table->string('port');
20 | $table->string('encryption');
21 | $table->string('driver')->default('smtp');
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('email_providers');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Migrations/2021_04_10_141626_create_email_accounts_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('email');
19 | $table->string('pass');
20 | $table->string('username')->nullable();
21 | $table->string('from_name')->nullable();
22 | $table->string('reply_to_mail')->nullable();
23 | $table->string('reply_to_name')->nullable();
24 | $table->bigInteger('provider_id')->unsigned()->nullable();
25 | $table->timestamps();
26 |
27 | $table->foreign('provider_id')->references('id')->on('email_providers');
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | *
34 | * @return void
35 | */
36 | public function down()
37 | {
38 | Schema::dropIfExists('email_accounts');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Models/EmailAccount.php:
--------------------------------------------------------------------------------
1 | belongsTo(EmailProvider::class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Models/EmailProvider.php:
--------------------------------------------------------------------------------
1 | app->bind('iwasherefirst2-laravelmultimail', function () {
26 | if(config()->has('multimail.mail_settings_class')){
27 | $configClass = config('multimail.mail_settings_class');
28 | $config = new $configClass();
29 | }
30 | else{
31 | $config = new FileConfigMailSettings();
32 | }
33 |
34 | return new MultiMailer($config);
35 | });
36 |
37 | $this->publishes([
38 | dirname(__DIR__) . '/publishable/config/multimail.php' => config_path('multimail.php'),
39 | ], 'config');
40 |
41 |
42 | $this->publishes([
43 | __DIR__.'/Migrations/' => database_path('migrations')
44 | ], 'migrations');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/MultiMailer.php:
--------------------------------------------------------------------------------
1 | config = $config;
40 | }
41 |
42 | /**
43 | * Create mailer from config/multimail.php
44 | * If its not a log driver, add AntiFloodPlugin.
45 | *
46 | * @param mixed $key string or array
47 | * @param int timeout
48 | * @param int frequency
49 | * @return \Illuminate\Mail\Mailer
50 | */
51 | public function getMailer($key, $timeout = null, $frequency = null, $fromName = null)
52 | {
53 | $config = $this->config->initialize($key);
54 |
55 | if (isset($this->mailers[$config->getEmail()])) {
56 | return $this->mailers[$config->getEmail()];
57 | }
58 |
59 | $swift_mailer = $this->getSwiftMailer($config);
60 |
61 | if (!$config->isLogDriver() && !empty($timeout) && !empty($frequency)) {
62 | $this->plugins[] = new Swift_Plugins_AntiFloodPlugin($frequency, $timeout);
63 | }
64 |
65 | $this->registerPlugins($swift_mailer);
66 |
67 | $view = app()->get('view');
68 | $events = app()->get('events');
69 |
70 | if (version_compare(app()->version(), '7.0.0') >= 0) {
71 | $mailer = new Mailer(config('app.name'), $view, $swift_mailer, $events);
72 | } else {
73 | $mailer = new Mailer($view, $swift_mailer, $events);
74 | }
75 |
76 | $mailer->alwaysFrom($config->getFromEmail(), $fromName ?? $config->getFromName());
77 |
78 | if (!empty($reply_mail = $config->getReplyEmail())) {
79 | $mailer->alwaysReplyTo($reply_mail, $config->getReplyEmail());
80 | }
81 |
82 | $this->mailers[$config->getEmail()] = $mailer;
83 |
84 | return $mailer;
85 | }
86 |
87 | /**
88 | * Send mail throug mail account form $mailer_name
89 | * @param MailableContract $mailable
90 | * @param [type] $mailer_name ]
91 | * @return [type] [description]
92 | */
93 | public function sendMail(MailableContract $mailable, $mailer_name, $fromName)
94 | {
95 | if (\App::runningUnitTests() && config('multimail.use_default_mail_facade_in_tests')) {
96 | return \Mail::send($mailable);
97 | }
98 |
99 | if (empty($mailer_name)) {
100 | return \Mail::send($mailable);
101 | }
102 | $mailer = $this->getMailer($mailer_name, null, null, $fromName);
103 |
104 | $mailable->send($mailer);
105 | }
106 |
107 | /**
108 | * [registerPlugin description]
109 | * @param [type] $plugin [description]
110 | * @return [type] [description]
111 | */
112 | public function registerPlugin($plugin)
113 | {
114 | $this->plugins[] = $plugin;
115 |
116 | // clear stored mailers, they may need new plugins.
117 | $this->mailers = [];
118 | }
119 |
120 | /**
121 | * [registerPlugin description]
122 | * @param [type] $plugin [description]
123 | * @return [type] [description]
124 | */
125 | public function clearPlugins()
126 | {
127 | $this->plugins = [];
128 |
129 | // clear stored mailers, they may need new plugins.
130 | $this->mailers = [];
131 | }
132 |
133 | public function getPlugins()
134 | {
135 | return $this->plugins;
136 | }
137 |
138 | public function queueMail(MailableContract $mailable, $mailer_name, $fromName)
139 | {
140 | // no mailer given, use default mailer
141 | if (empty($mailer_name)) {
142 | return \Mail::queue($mailable);
143 | }
144 |
145 | Jobs\SendMailJob::dispatch($mailer_name, $mailable, $fromName)->onQueue($mailable->queue ?? null);
146 | }
147 |
148 | /**
149 | * Begin the process of mailing a mailable class instance.
150 | *
151 | * @param mixed $users
152 | * @return \IWasHereFirst2\MultiMail\PendingMail
153 | */
154 | public function to($users)
155 | {
156 | return (new PendingMail())->to($users);
157 | }
158 |
159 | /**
160 | * Begin the process of mailing a mailable class instance.
161 | *
162 | * @param mixed $users
163 | * @return \IWasHereFirst2\MultiMail\PendingMail
164 | */
165 | public function from(string $mailerKey, $fromName = null)
166 | {
167 | return (new PendingMail())->from($mailerKey, $fromName);
168 | }
169 |
170 | /**
171 | * Begin the process of mailing a mailable class instance.
172 | *
173 | * @param mixed $users
174 | * @return \IWasHereFirst2\MultiMail\PendingMail
175 | */
176 | public function cc($users)
177 | {
178 | return (new PendingMail())->cc($users);
179 | }
180 |
181 | /**
182 | * Begin the process of mailing a mailable class instance.
183 | *
184 | * @param mixed $users
185 | * @return \IWasHereFirst2\MultiMail\PendingMail
186 | */
187 | public function bcc($users)
188 | {
189 | return (new PendingMail())->bcc($users);
190 | }
191 |
192 | /**
193 | * Begin the process of mailing a mailable class instance.
194 | *
195 | * @param string locale 2 char
196 | * @return \IWasHereFirst2\MultiMail\PendingMail
197 | */
198 | public function locale($locale)
199 | {
200 | return (new PendingMail())->locale($locale);
201 | }
202 |
203 | /**
204 | * Send mail or queue if implements ShouldQueue
205 | *
206 | * @param Mailable
207 | * @return \IWasHereFirst2\MultiMail\MultiMailer
208 | */
209 | public function send(Mailable $mailable)
210 | {
211 | return (new PendingMail())->send($mailable);
212 | }
213 |
214 | /**
215 | * Queue mail
216 | *
217 | * @param Mailable
218 | * @return \IWasHereFirst2\MultiMail\MultiMailer
219 | */
220 | public function queue(Mailable $mailable)
221 | {
222 | return (new PendingMail())->queue($mailable);
223 | }
224 |
225 | /**
226 | * Create SwiftMailer with timeout/frequency. Timeout/frequency is ignored
227 | * when Log Driver is used.
228 | *
229 | * @param array
230 | * @return Swift_Mailer
231 | */
232 | protected function getSwiftMailer($config)
233 | {
234 | if ($config->isLogDriver()) {
235 | $transport = TransportManager::createLogDriver();
236 |
237 | return new Swift_Mailer($transport);
238 | }
239 |
240 | $transport = TransportManager::createSmtpDriver($config);
241 |
242 | return new Swift_Mailer($transport);
243 | }
244 |
245 | protected function registerPlugins($swift_mailer)
246 | {
247 | if (!empty($this->plugins)) {
248 | foreach ($this->plugins as $plugin) {
249 | $swift_mailer->registerPlugin($plugin);
250 | }
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/PendingMail.php:
--------------------------------------------------------------------------------
1 | .., 'email' => ..., 'reply_to']
13 | *
14 | * @var string
15 | */
16 | protected $fromMailer;
17 |
18 | /**
19 | * The locale of the message.
20 | *
21 | * @var array
22 | */
23 | protected $locale;
24 |
25 | /**
26 | * The "to" recipients of the message.
27 | *
28 | * @var array
29 | */
30 | protected $to = [];
31 |
32 | /**
33 | * The "cc" recipients of the message.
34 | *
35 | * @var array
36 | */
37 | protected $cc = [];
38 |
39 | /**
40 | * The "bcc" recipients of the message.
41 | *
42 | * @var array
43 | */
44 | protected $bcc = [];
45 |
46 | /**
47 | * Overrwrites name of from mail cofnig
48 | *
49 | * @var ?string
50 | */
51 | protected $fromName = null;
52 |
53 | /**
54 | * Set the locale of the message.
55 | *
56 | * @param string $locale
57 | * @return $this
58 | */
59 | public function locale($locale)
60 | {
61 | $this->locale = $locale;
62 |
63 | return $this;
64 | }
65 |
66 | /**
67 | * Set the recipients of the message.
68 | *
69 | * @param mixed $users
70 | * @return $this
71 | */
72 | public function to($users)
73 | {
74 | $this->to = $users;
75 |
76 | return $this;
77 | }
78 |
79 | /**
80 | * Determine from mailer.
81 | *
82 | * @param string mailer name
83 | * @return $this
84 | */
85 | public function from($mailerKey, $fromName = null)
86 | {
87 | $this->fromName = $fromName;
88 | $this->fromMailer = $mailerKey;
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * Set the addtional recipients of the message.
95 | *
96 | * @param mixed $users
97 | * @return $this
98 | */
99 | public function cc($users)
100 | {
101 | $this->cc = $users;
102 |
103 | return $this;
104 | }
105 |
106 | /**
107 | * Set the hidden recipients of the message.
108 | *
109 | * @param mixed $users
110 | * @return $this
111 | */
112 | public function bcc($users)
113 | {
114 | $this->bcc = $users;
115 |
116 | return $this;
117 | }
118 |
119 | /**
120 | * Send a new mailable message instance.
121 | *
122 | * @param \Illuminate\Mail\Mailable $mailable
123 | * @return mixed
124 | */
125 | public function send(Mailable $mailable)
126 | {
127 | if ($mailable instanceof ShouldQueue) {
128 | return $this->queue($mailable);
129 | }
130 |
131 | return $this->sendNow($this->fill($mailable));
132 | }
133 |
134 | /**
135 | * Send a mailable message immediately.
136 | *
137 | * @param \Illuminate\Mail\Mailable $mailable
138 | * @return mixed
139 | */
140 | public function sendNow(Mailable $mailable)
141 | {
142 | $mailer = $this->fromMailer ?? optional($mailable)->fromMailer;
143 |
144 | return MultiMail::sendMail($this->fill($mailable), $mailer, $this->fromName);
145 | }
146 |
147 | /**
148 | * Push the given mailable onto the queue.
149 | *
150 | * @param \Illuminate\Mail\Mailable $mailable
151 | * @return mixed
152 | */
153 | public function queue(Mailable $mailable)
154 | {
155 | $mailer = $this->fromMailer ?? optional($mailable)->fromMailer;
156 |
157 | return MultiMail::queueMail($this->fill($mailable), $mailer, $this->fromName);
158 | }
159 |
160 | /**
161 | * Populate the mailable with the addresses.
162 | *
163 | * @param \Illuminate\Mail\Mailable $mailable
164 | * @return \Illuminate\Mail\Mailable
165 | */
166 | protected function fill(Mailable $mailable)
167 | {
168 | if (!empty($this->locale)) {
169 | $mailable->locale($this->locale);
170 | }
171 |
172 | return $mailable->to($this->to)
173 | ->cc($this->cc)
174 | ->bcc($this->bcc);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/TransportManager.php:
--------------------------------------------------------------------------------
1 | getProvider();
20 | $setting = $config->getSetting();
21 |
22 | $transport = new Swift_SmtpTransport($provider['host'], $provider['port'], $provider['encryption']);
23 | $transport->setUsername($setting['username'] ?? $config->getEmail());
24 | $transport->setPassword($setting['pass']);
25 |
26 | return self::configureSmtpDriver($transport, $provider);
27 | }
28 |
29 | /**
30 | * Configure the additional SMTP driver options.
31 | *
32 | * @param \Swift_SmtpTransport $transport
33 | * @param array $config
34 | * @return \Swift_SmtpTransport
35 | */
36 | protected static function configureSmtpDriver($transport, $config)
37 | {
38 | if (isset($config['stream'])) {
39 | $transport->setStreamOptions($config['stream']);
40 | }
41 |
42 | if (isset($config['source_ip'])) {
43 | $transport->setSourceIp($config['source_ip']);
44 | }
45 |
46 | if (isset($config['local_domain'])) {
47 | $transport->setLocalDomain($config['local_domain']);
48 | }
49 |
50 | return $transport;
51 | }
52 |
53 | /**
54 | * Create LOG Transport.
55 | *
56 | * @return LogTransport
57 | */
58 | public static function createLogDriver()
59 | {
60 | return new LogTransport(app()->make(LoggerInterface::class));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/.env.example:
--------------------------------------------------------------------------------
1 | MAIL_DRIVER_SMTP=smtp
2 | MAIL_HOST_SMTP=smtp.mailtrap.io
3 | MAIL_PORT_SMTP=2525
4 |
5 | MAIL_USERNAME_SMTP=
6 | MAIL_PASSWORD_SMTP=
7 | MAIL_ENCRYPTION_SMTP=TLS
8 |
9 | MAILTRAP_API_KEY =
10 | MAILTRAP_INBOX_ID =
11 |
--------------------------------------------------------------------------------
/tests/Fixtures/view.blade.php:
--------------------------------------------------------------------------------
1 | {{__('nom')}}
2 |
--------------------------------------------------------------------------------
/tests/Integration/MultiMailDatabaseTest.php:
--------------------------------------------------------------------------------
1 | loadMigrationsFrom(realpath(__DIR__ . '/../../src/Migrations'));
23 |
24 | $this->artisan('migrate',['--database' => 'testbench'])
25 | ->run();
26 |
27 | $provider = EmailProvider::create([
28 | 'host' => env('MAIL_HOST_SMTP'),
29 | 'port' => env('MAIL_PORT_SMTP'),
30 | 'encryption' => env('MAIL_ENCRYPTION_SMTP'),
31 | 'driver' => env('MAIL_DRIVER_SMTP'),
32 | ]);
33 |
34 | EmailAccount::create([
35 | 'email' => static::FROM,
36 | 'pass' => env('MAIL_PASSWORD_SMTP'),
37 | 'username' => env('MAIL_USERNAME_SMTP'),
38 | 'from_name' => 'Rayn Roogen',
39 | 'provider_id' => $provider->id
40 | ]);
41 |
42 | $to = 'foo-fighter@foo.com';
43 | $locale = 'de';
44 | $from = static::FROM;
45 | $bcc = ['oki@foo.berlin', 'rooky@mooky.de'];
46 |
47 | MultiMail::to($to)
48 | ->locale($locale)
49 | ->from($from)
50 | ->bcc($bcc)
51 | ->send(new TestMail());
52 |
53 | $message = $this->findMessage('TestMail Subject');
54 |
55 | $this->assertEquals('Rayn Roogen', $message[0]['from_name']);
56 | $this->emptyInbox();
57 | }
58 |
59 | /**
60 | * Define environment setup.
61 | *
62 | * @param \Illuminate\Foundation\Application $app
63 | * @return void
64 | */
65 | protected function getEnvironmentSetUp($app)
66 | {
67 | // Setup default database to use sqlite :memory:
68 | $app['config']->set('database.default', 'testbench');
69 | $app['config']->set('database.connections.testbench', [
70 | 'driver' => 'sqlite',
71 | 'database' => ':memory:',
72 | 'prefix' => '',
73 | ]);
74 |
75 | $app->useEnvironmentPath(__DIR__ . '/..');
76 | $app->bootstrapWith([LoadEnvironmentVariables::class]);
77 |
78 | $app['config']->set('multimail.mail_settings_class',
79 | DatabaseConfigMailSettings::class);
80 |
81 | $app['config']->set('multimail.provider.default', [
82 | 'driver' => 'log',
83 | ]);
84 |
85 | $app['config']->set('mail.driver', 'log');
86 |
87 | View::addLocation(__DIR__ . '/../Fixtures');
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Integration/MultiMailTest.php:
--------------------------------------------------------------------------------
1 | cc($cc)
26 | ->locale($locale)
27 | ->from($from)
28 | ->bcc($bcc)
29 | ->send(new TestMail());
30 |
31 | $this->assertNotNull($this->emails);
32 | $this->assertEquals(1, count($this->emails));
33 | }
34 |
35 | /** @test */
36 | public function check_if_from_name_works()
37 | {
38 | MultiMail::clearPlugins();
39 |
40 | MultiMail::to('test@bar.com')->from(['name' => 'Adam', 'email' => 'test@fake.de'])->send(new TestMail());
41 |
42 | $this->assertEquals([], MultiMail::getPlugins());
43 | }
44 |
45 | /** @test */
46 | public function send_mail_directly()
47 | {
48 | MultiMail::send(new TestMailIncludingFrom());
49 |
50 | $this->assertNotNull($this->emails);
51 | $this->assertEquals(1, count($this->emails));
52 | }
53 |
54 | /** @test */
55 | public function send_mail_implementing_queue()
56 | {
57 | MultiMail::send(new QueueTestMailIncludingFrom());
58 |
59 | $this->assertNotNull($this->emails);
60 | $this->assertEquals(1, count($this->emails));
61 | }
62 |
63 | /** @test */
64 | public function send_mail_through_queue()
65 | {
66 | MultiMail::queue(new TestMailIncludingFrom());
67 |
68 | $this->assertNotNull($this->emails);
69 | $this->assertEquals(1, count($this->emails));
70 | }
71 |
72 | /** @test */
73 | public function send_mail_through_default()
74 | {
75 | MultiMail::send(new TestMail());
76 |
77 | $this->assertNotNull($this->emails);
78 | $this->assertEquals(1, count($this->emails));
79 | }
80 |
81 | /** @test */
82 | public function send_mail_through_queue_default()
83 | {
84 | MultiMail::queue(new TestMail());
85 |
86 | $this->assertNotNull($this->emails);
87 | $this->assertEquals(1, count($this->emails));
88 | }
89 |
90 | /**
91 | * Define environment setup.
92 | *
93 | * @param \Illuminate\Foundation\Application $app
94 | * @return void
95 | */
96 | protected function getEnvironmentSetUp($app)
97 | {
98 | $app['config']->set('multimail.emails',
99 | ['test@fake.de' => [
100 | 'pass' => 'fakepass',
101 | 'username' => 'fakeusername',
102 | 'from' => 'Who knows',
103 | 'reply_to_mail' => 'bs@web.de',
104 | ]]);
105 |
106 | $app['config']->set('multimail.provider.default', [
107 | 'driver' => 'log',
108 | ]);
109 |
110 | $app['config']->set('mail.driver', 'log');
111 |
112 | View::addLocation(__DIR__ . '/../Fixtures');
113 | }
114 | }
115 |
116 | class TestMailIncludingFrom extends Mailable
117 | {
118 | public function __construct()
119 | {
120 | $this->fromMailer = MultiMailTest::FROM;
121 | }
122 |
123 | /**
124 | * Build the message.
125 | *
126 | * @return $this
127 | */
128 | public function build()
129 | {
130 | $this->subject = 'TestMail Subject';
131 | return $this->view('view');
132 | }
133 | }
134 |
135 | class QueueTestMailIncludingFrom extends Mailable implements ShouldQueue
136 | {
137 | public function __construct()
138 | {
139 | $this->fromMailer = MultiMailTest::FROM;
140 | }
141 |
142 | /**
143 | * Build the message.
144 | *
145 | * @return $this
146 | */
147 | public function build()
148 | {
149 | $this->subject = 'TestMail Subject';
150 | return $this->view('view');
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/tests/Integration/SMPTTest.php:
--------------------------------------------------------------------------------
1 | locale($locale)
27 | ->from($from)
28 | ->send(new TestMail());
29 |
30 | $this->assertTrue($this->messageExists('TestMail Subject'));
31 |
32 | $this->emptyInbox();
33 | }
34 |
35 | /** @test */
36 | public function check_smtp_mail_from_name()
37 | {
38 | $to = 'test@bar.com';
39 | $locale = 'de';
40 | $from = static::FROM;
41 | MultiMail::to($to)
42 | ->locale($locale)
43 | ->from($from)
44 | ->send(new TestMail());
45 |
46 | $message = $this->findMessage('TestMail Subject');
47 |
48 | $this->assertEquals('Adam Nielsen', $message[0]['from_name']);
49 | $this->emptyInbox();
50 | }
51 |
52 | /** @test */
53 | public function check_if_smtp_mail_can_configure_from()
54 | {
55 | $to = 'test@bar.com';
56 | $locale = 'de';
57 | $from = static::FROM;
58 | MultiMail::to($to)
59 | ->locale($locale)
60 | ->from($from, 'Backarony Mockoli')
61 | ->send(new TestMail());
62 |
63 | $message = $this->findMessage('TestMail Subject');
64 |
65 | $this->assertEquals('Backarony Mockoli', $message[0]['from_name']);
66 | $this->emptyInbox();
67 | }
68 |
69 | /** @test */
70 | public function get_mailer_with_antifloodplugin()
71 | {
72 | $mailer = MultiMail::getMailer(static::FROM, 1, 1);
73 |
74 | $this->assertEquals(2, count(MultiMail::getPlugins()));
75 | }
76 |
77 | /**
78 | * Define environment setup.
79 | *
80 | * @param \Illuminate\Foundation\Application $app
81 | * @return void
82 | */
83 | protected function getEnvironmentSetUp($app)
84 | {
85 | $app->useEnvironmentPath(__DIR__ . '/..');
86 | $app->bootstrapWith([LoadEnvironmentVariables::class]);
87 |
88 | parent::getEnvironmentSetUp($app);
89 |
90 | // Setup default database to use sqlite :memory:
91 | $app['config']->set('multimail.emails',
92 | ['smtp@fake.de' => [
93 | 'pass' => env('MAIL_PASSWORD_SMTP'),
94 | 'username' => env('MAIL_USERNAME_SMTP'),
95 | 'from_name' => 'Adam Nielsen',
96 | 'provider' => 'smtp',
97 | ]]);
98 |
99 | $app['config']->set('multimail.provider.smtp', [
100 | 'host' => env('MAIL_HOST_SMTP'),
101 | 'port' => env('MAIL_PORT_SMTP'),
102 | 'encryption' => env('MAIL_ENCRYPTION_SMTP'),
103 | 'driver' => env('MAIL_DRIVER_SMTP'),
104 | ]);
105 |
106 | View::addLocation(__DIR__ . '/../Fixtures');
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/tests/Integration/TestMail.php:
--------------------------------------------------------------------------------
1 | subject = 'TestMail Subject';
17 | return $this->view('view');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | registerPlugin(new TestingMailEventListener($this));
23 | }
24 |
25 | public function addEmail(Swift_Message $email)
26 | {
27 | $this->emails[] = $email;
28 | }
29 |
30 | /**
31 | * add the package provider
32 | *
33 | * @param $app
34 | * @return array
35 | */
36 | protected function getPackageProviders($app)
37 | {
38 | return [MultiMailServiceProvider::class];
39 | }
40 | }
41 |
42 | class TestingMailEventListener implements Swift_Events_EventListener
43 | {
44 | protected $test;
45 |
46 | public function __construct($test)
47 | {
48 | $this->test = $test;
49 | }
50 |
51 | public function getDebugInfo()
52 | {
53 | return 'This is the Custom Test Case Event Plugin';
54 | }
55 |
56 | public function beforeSendPerformed($event)
57 | {
58 | $this->test->addEmail($event->getMessage());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/Traits/MailTrap.php:
--------------------------------------------------------------------------------
1 |
9 | * Permission is hereby granted, free of charge, to any person
10 | * obtaining a copy of this software and associated documentation
11 | * files (the "Software"), to deal in the Software without
12 | * restriction, including without limitation the rights to use,
13 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | * copies of the Software, and to permit persons to whom the
15 | * Software is furnished to do so, subject to the following
16 | * conditions:
17 | *
18 | * The above copyright notice and this permission notice shall be
19 | * included in all copies or substantial portions of the Software.
20 | *
21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 | * OTHER DEALINGS IN THE SOFTWARE.
29 | */
30 |
31 | namespace IWasHereFirst2\LaravelMultiMail\Tests\Traits;
32 |
33 | use \Exception;
34 | use GuzzleHttp\Client;
35 | use Illuminate\Support\Facades\Config;
36 |
37 | trait MailTrap
38 | {
39 | /**
40 | * The MailTrap configuration.
41 | *
42 | * @var integer
43 | */
44 | protected $mailTrapInboxId;
45 |
46 | /**
47 | * The MailTrap API Key.
48 | *
49 | * @var string
50 | */
51 | protected $mailTrapApiKey;
52 |
53 | /**
54 | * The Guzzle client.
55 | *
56 | * @var Client
57 | */
58 | protected $client;
59 |
60 | /**
61 | *
62 | * Empty the MailTrap inbox.
63 | *
64 | * @AfterScenario @mail
65 | */
66 | public function emptyInbox()
67 | {
68 | $this->requestClient()->patch($this->getMailTrapCleanUrl());
69 | }
70 |
71 | /**
72 | * Get the configuration for MailTrap.
73 | *
74 | * @param integer|null $inboxId
75 | * @throws Exception
76 | */
77 | protected function applyMailTrapConfiguration($inboxId = null)
78 | {
79 | $this->mailTrapInboxId = env('MAILTRAP_INBOX_ID');
80 | $this->mailTrapApiKey = env('MAILTRAP_API_KEY');
81 | }
82 |
83 | /**
84 | * Fetch a MailTrap inbox.
85 | *
86 | * @param integer|null $inboxId
87 | * @return mixed
88 | * @throws RuntimeException
89 | */
90 | protected function fetchInbox($inboxId = null)
91 | {
92 | if (! $this->alreadyConfigured()) {
93 | $this->applyMailTrapConfiguration($inboxId);
94 | }
95 |
96 | $body = $this->requestClient()
97 | ->get($this->getMailTrapMessagesUrl())
98 | ->getBody();
99 |
100 | return $this->parseJson($body);
101 | }
102 |
103 | /**
104 | * Get the MailTrap messages endpoint.
105 | *
106 | * @return string
107 | */
108 | protected function getMailTrapMessagesUrl()
109 | {
110 | return "/api/v1/inboxes/{$this->mailTrapInboxId}/messages";
111 | }
112 |
113 | /**
114 | * Get the MailTrap "empty inbox" endpoint.
115 | *
116 | * @return string
117 | */
118 | protected function getMailTrapCleanUrl()
119 | {
120 | return "/api/v1/inboxes/{$this->mailTrapInboxId}/clean";
121 | }
122 |
123 | /**
124 | * Determine if MailTrap config has been retrieved yet.
125 | *
126 | * @return boolean
127 | */
128 | protected function alreadyConfigured()
129 | {
130 | return $this->mailTrapApiKey;
131 | }
132 |
133 | /**
134 | * Request a new Guzzle client.
135 | *
136 | * @return Client
137 | */
138 | protected function requestClient()
139 | {
140 | if (! $this->client) {
141 | $this->client = new Client([
142 | 'base_uri' => 'https://mailtrap.io',
143 | 'headers' => ['Api-Token' => $this->mailTrapApiKey],
144 | ]);
145 | }
146 |
147 | return $this->client;
148 | }
149 |
150 | /**
151 | * @param $body
152 | * @return array|mixed
153 | * @throws RuntimeException
154 | */
155 | protected function parseJson($body)
156 | {
157 | $data = json_decode((string) $body, true);
158 |
159 | if (JSON_ERROR_NONE !== json_last_error()) {
160 | throw new RuntimeException('Unable to parse response body into JSON: ' . json_last_error());
161 | }
162 |
163 | return $data === null ? [] : $data;
164 | }
165 |
166 | /**
167 | * Search Messages Url
168 | *
169 | * @param string $query Search query
170 | * @return string
171 | */
172 | protected function searchInboxMessagesUrl($query)
173 | {
174 | return "/api/v1/inboxes/{$this->mailTrapInboxId}/messages?search=" . $query;
175 | }
176 |
177 | /**
178 | * Find and fetch a Message By Query.
179 | *
180 | * @param integer $query Query
181 | * @return mixed
182 | * @throws RuntimeException
183 | */
184 | protected function findMessage($query)
185 | {
186 | if (! $this->alreadyConfigured()) {
187 | $this->applyMailTrapConfiguration();
188 | }
189 |
190 | $counter = 0;
191 |
192 | do{
193 | $body = $this->requestClient()
194 | ->get($this->searchInboxMessagesUrl($query))
195 | ->getBody();
196 |
197 | $body = $this->parseJson($body);
198 | $counter++;
199 |
200 | if($counter >= 50){
201 | return $body;
202 | }
203 | }while(empty($body));
204 |
205 | return $body;
206 | }
207 |
208 | /**
209 | * Check if a message exists based on a string query.
210 | *
211 | * @param string $query Query string
212 | * @return mixed
213 | * @throws RuntimeException
214 | */
215 | protected function messageExists($query)
216 | {
217 | $messages = $this->findMessage($query);
218 |
219 | return count($messages) > 0;
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/tests/Unit/ConfigTest.php:
--------------------------------------------------------------------------------
1 | expectException(EmailNotInConfigException::class);
17 |
18 | (new FileConfigMailSettings())->initialize('unknown key');
19 | }
20 |
21 | /** @test */
22 | public function create_config_with_invalid_array_key()
23 | {
24 | $this->expectException(InvalidConfigKeyException::class);
25 |
26 | (new FileConfigMailSettings())->initialize(['unknown key']);
27 | }
28 |
29 | /** @test */
30 | public function create_config_with_valid_key()
31 | {
32 | $config = (new FileConfigMailSettings())->initialize(['name' => 'Adam', 'email' => 'test@fake.de']);
33 |
34 | $this->assertEquals('test@fake.de', $config->getFromEmail());
35 | }
36 |
37 | /** @test */
38 | /*
39 | public function test_non_existing_mail()
40 | {
41 | $this->expectException(\Exception::class);
42 | $config = new Config(['name' => 'Adam', 'email' => 'test@faki.de']);
43 | }*/
44 |
45 | /** @test */
46 | public function get_reply_name()
47 | {
48 | $config = (new FileConfigMailSettings())->initialize(['name' => 'Adam', 'email' => 'test@fake.de']);
49 |
50 | $this->assertEquals('max', $config->getReplyName());
51 | }
52 |
53 | /** @test */
54 | public function load_invalid_default()
55 | {
56 | $this->expectException(NoDefaultException::class);
57 | $config = (new FileConfigMailSettings())->initialize(['name' => 'Adam', 'email' => 'test@empty.de']);
58 | }
59 |
60 | /** @test */
61 | public function load_valid_default()
62 | {
63 | app()['config']->set('multimail.emails.default',
64 | [
65 | 'pass' => 'fakepass',
66 | 'username' => 'fakeusername',
67 | 'from' => 'Who knows',
68 | 'reply_to_mail' => 'bs@web.de',
69 | 'reply_to_name' => 'max',
70 | ]);
71 |
72 | app()['config']->set('multimail.provider.default', [
73 | 'driver' => 'log',
74 | ]);
75 |
76 | $config = (new FileConfigMailSettings())->initialize(['name' => 'Adam', 'email' => 'test@empty.de']);
77 |
78 | $this->assertNotNull($config);
79 | }
80 |
81 | /**
82 | * Define environment setup.
83 | *
84 | * @param \Illuminate\Foundation\Application $app
85 | * @return void
86 | */
87 | protected function getEnvironmentSetUp($app)
88 | {
89 | $app['config']->set('multimail.emails',
90 | ['test@fake.de' => [
91 | 'pass' => 'fakepass',
92 | 'username' => 'fakeusername',
93 | 'from' => 'Who knows',
94 | 'reply_to_mail' => 'bs@web.de',
95 | 'reply_to_name' => 'max',
96 | ],
97 | 'test@empty.de' => [
98 | 'pass' => '',
99 | 'username' => '',
100 | 'from' => 'Who knows',
101 | 'reply_to_mail' => 'bs@web.de',
102 | 'reply_to_name' => 'max',
103 | ], ]);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/Unit/MultiMailTest.php:
--------------------------------------------------------------------------------
1 | assertContainsOnlyInstancesOf(PendingMail::class, $classes);
27 | }
28 |
29 | /** @test */
30 | public function check_if_plugins_deletable()
31 | {
32 | MultiMail::clearPlugins();
33 |
34 | $this->assertEquals([], MultiMail::getPlugins());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Unit/PendingMailTest.php:
--------------------------------------------------------------------------------
1 | cc($cc)
21 | ->locale($locale)
22 | ->from($from)
23 | ->bcc($bcc);
24 |
25 | $pendingMail2 = MultiMail::locale($locale)
26 | ->from($from)
27 | ->to($to)
28 | ->cc($cc)
29 | ->bcc($bcc);
30 |
31 | $this->assertEquals($pendingMail, $pendingMail2);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------