├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json └── src └── SMTP.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.php] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vendor (e.g. Composer) 2 | vendor 3 | 4 | # Visual Studio 5 | .vscode 6 | 7 | # macOS 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Brandon Nifong 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 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, 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP SMTP 2 | 3 | [](https://packagist.org/packages/log1x/wp-smtp) [](https://packagist.org/packages/log1x/wp-smtp) 4 | 5 | WP SMTP is a simple Composer package for handling WordPress SMTP with `.env`. No admin menus or [other bloat](https://blog.sucuri.net/2019/03/0day-vulnerability-in-easy-wp-smtp-affects-thousands-of-sites.html). Just a simple admin notice to verify your connection when needed and the ability to do a simple task WordPress should probably be handling natively. 6 | 7 | ## Getting Started 8 | 9 | ### Requirements 10 | 11 | - [Sage](https://github.com/roots/sage) >= 9.0 12 | - [Bedrock](https://github.com/roots/bedrock) 13 | - [PHP](https://secure.php.net/manual/en/install.php) >= 7.1.3 14 | - [Composer](https://getcomposer.org/download/) 15 | 16 | ### Installation 17 | 18 | Install via Composer: 19 | 20 | ```sh 21 | composer require log1x/wp-smtp 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### Configuration 27 | 28 | All configuration goes into `.env`. 29 | 30 | #### Required 31 | 32 | ```conf 33 | WP_SMTP_HOST=mail.example.com # Host 34 | WP_SMTP_USERNAME=example # Username 35 | WP_SMTP_PASSWORD=secure123 # Password 36 | ``` 37 | 38 | #### Optional 39 | 40 | ```conf 41 | WP_SMTP_PORT=587 # Port 42 | WP_SMTP_PROTOCOL=tls # Protocol 43 | WP_SMTP_TIMEOUT=10 # Timeout 44 | WP_SMTP_FORCEFROM=example@example.com # Force From Email 45 | WP_SMTP_FORCEFROMNAME=Example # Force From Name 46 | ``` 47 | 48 | #### Mailhog 49 | 50 | ```conf 51 | WP_SMTP_HOST=localhost 52 | WP_SMTP_PORT=1025 53 | WP_SMTP_PROTOCOL=false 54 | ``` 55 | 56 | ## Debugging 57 | 58 | > SMTP connect() failed. 59 | 60 | This error means the initial connection to your host/port failed. 61 | 62 | ## Contributing 63 | 64 | Contributing whether it be through PRs, reporting an issue, or suggesting an idea is encouraged and appreciated. When contributing code, please follow the existing code style. 65 | 66 | If you're feeling generous, I also take contributions in the form of [coffee & energy drinks](https://www.buymeacoffee.com/log1x). 67 | 68 | ## License 69 | 70 | WP SMTP is provided under the [MIT License](https://github.com/log1x/wp-smtp/blob/master/LICENSE.md). 71 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "log1x/wp-smtp", 3 | "description": "Simple package for handling WordPress SMTP with .env when using the Roots stack.", 4 | "type": "package", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Brandon Nifong", 9 | "email": "brandon@tendency.me" 10 | } 11 | ], 12 | "autoload": { 13 | "files": [ 14 | "src/SMTP.php" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SMTP.php: -------------------------------------------------------------------------------- 1 | true, 16 | 'ssl' => [ 17 | 'verify_peer' => false, 18 | 'verify_peer_name' => false, 19 | 'allow_self_signed' => true 20 | ], 21 | 'timeout' => 10, 22 | 'host' => '', 23 | 'port' => 587, 24 | 'protocol' => 'tls', 25 | 'username' => '', 26 | 'password' => '', 27 | 'forceFrom' => '', 28 | 'forceFromName' => '', 29 | ]; 30 | 31 | /** 32 | * Constructor 33 | */ 34 | public function __construct() 35 | { 36 | $this->admin(); 37 | $this->mail(); 38 | } 39 | 40 | /** 41 | * Config 42 | * 43 | * @return object 44 | */ 45 | protected function config() 46 | { 47 | $config = collect([ 48 | 'host' => env('WP_SMTP_HOST'), 49 | 'port' => env('WP_SMTP_PORT'), 50 | 'protocol' => env('WP_SMTP_PROTOCOL'), 51 | 'username' => env('WP_SMTP_USERNAME'), 52 | 'password' => env('WP_SMTP_PASSWORD'), 53 | 'timeout' => env('WP_SMTP_TIMEOUT'), 54 | 'forceFrom' => env('WP_SMTP_FORCEFROM'), 55 | 'forceFromName' => env('WP_SMTP_FORCEFROMNAME') 56 | ])->filter(); 57 | 58 | return (object) collect($this->config) 59 | ->merge($config) 60 | ->all(); 61 | } 62 | 63 | /** 64 | * Validate Hash 65 | * 66 | * @return boolean 67 | */ 68 | protected function validate() 69 | { 70 | return $this->hash() === get_option('wp_mail_verify'); 71 | } 72 | 73 | /** 74 | * Hash 75 | * 76 | * @return string 77 | */ 78 | protected function hash() 79 | { 80 | return hash('crc32', serialize($this->config())); 81 | } 82 | 83 | /** 84 | * Notice 85 | * 86 | * @param string $message 87 | * @param string $type 88 | * @param boolean $dismissible 89 | * @return void 90 | */ 91 | protected function notice($message, $type = 'info', $dismissible = false) 92 | { 93 | add_action('admin_notices', function () use ($message, $type, $dismissible) { 94 | printf( 95 | '
%2$s
.env
and click here to test your configuration.',
116 | 'error'
117 | );
118 | }
119 |
120 | if (! get_option('wp_mail_verify')) {
121 | return $this->notice(
122 | 'WP SMTP credentials found. Please click here to test your configuration.'
123 | );
124 | }
125 |
126 | if (! $this->validate()) {
127 | return $this->notice(
128 | 'WP SMTP has detected a change in your credentials. Please click here to test your configuration.'
129 | );
130 | }
131 | }
132 |
133 | /**
134 | * Mail
135 | *
136 | * @return void
137 | */
138 | protected function mail()
139 | {
140 | if ($this->config()->username && $this->config()->password && $this->config()->host) {
141 | add_action('phpmailer_init', function ($mail) {
142 | $mail->isSMTP();
143 | $mail->SMTPAuth = $this->config()->auth;
144 | $mail->SMTPSecure = $this->config()->protocol;
145 | $mail->SMTPOptions = ['ssl' => $this->config()->ssl];
146 | $mail->Timeout = $this->config()->timeout;
147 |
148 | $mail->Host = $this->config()->host;
149 | $mail->Port = $this->config()->port;
150 | $mail->Username = $this->config()->username;
151 | $mail->Password = $this->config()->password;
152 |
153 | $mail->setFrom($mail->From, $mail->FromName);
154 |
155 | if ($this->config()->forceFrom && $this->config()->forceFromName) {
156 | $mail->setFrom($this->config()->forceFrom, $this->config()->forceFromName);
157 | }
158 | }, PHP_INT_MAX);
159 | }
160 | }
161 |
162 | /**
163 | * Verify SMTP Credentials
164 | *
165 | * @return void
166 | */
167 | protected function verify()
168 | {
169 | require_once(ABSPATH . WPINC . '/PHPMailer/Exception.php');
170 | require_once(ABSPATH . WPINC . '/PHPMailer/PHPMailer.php');
171 | require_once(ABSPATH . WPINC . '/PHPMailer/SMTP.php');
172 |
173 | $mail = new \PHPMailer\PHPMailer\PHPMailer(true);
174 |
175 | try {
176 | $mail->isSMTP();
177 | $mail->SMTPAuth = $this->config()->auth;
178 | $mail->SMTPOptions = ['ssl' => $this->config()->ssl];
179 | $mail->SMTPSecure = $this->config()->protocol;
180 | $mail->SMTPAutoTLS = false;
181 |
182 | $mail->Host = $this->config()->host;
183 | $mail->Username = $this->config()->username;
184 | $mail->Password = $this->config()->password;
185 | $mail->Port = $this->config()->port;
186 | $mail->Timeout = $this->config()->timeout;
187 |
188 | $mail->setFrom('no-reply@'.preg_replace('/https?:\/\/(www\.)?/', '', get_bloginfo('url')), get_bloginfo('name'));
189 | $mail->AddAddress(wp_get_current_user()->user_email);
190 |
191 | if ($this->config()->forceFrom && $this->config()->forceFromName) {
192 | $mail->setFrom($this->config()->forceFrom, $this->config()->forceFromName);
193 | }
194 |
195 | $mail->CharSet = get_bloginfo('charset');
196 | $mail->Subject = 'WP SMTP Validation';
197 | $mail->Body = 'Success.';
198 |
199 | $mail->Send();
200 | $mail->ClearAddresses();
201 | $mail->ClearAllRecipients();
202 | } catch (\PHPMailer\PHPMailer\Exception $error) {
203 | return $this->notice(
204 | $error->errorMessage(),
205 | 'error',
206 | true
207 | );
208 | } catch (Exception $error) {
209 | return $this->notice(
210 | $error->getMessage(),
211 | 'error',
212 | true
213 | );
214 | }
215 |
216 | if (update_option('wp_mail_verify', $this->hash())) {
217 | return $this->notice(
218 | 'WP SMTP connection successful!',
219 | 'success',
220 | true
221 | );
222 | }
223 | }
224 | }
225 |
226 | if (function_exists('add_action')) {
227 | return new SMTP;
228 | }
229 |
--------------------------------------------------------------------------------