From 02bcc732619381e19070a43117bf0e3e98aa1c7b Mon Sep 17 00:00:00 2001 From: Hosteroid Date: Tue, 14 Oct 2025 00:27:50 +0300 Subject: [PATCH] Refactor email handling to use EmailHelper and auto-detect encryption Extracted all email sending logic into a new EmailHelper class for centralized management and improved error handling. Updated AuthController, SettingsController, and EmailChannel to use EmailHelper for sending emails and logging. Added auto-detection of SMTP encryption based on port in both backend (SettingsController) and frontend (settings view), improving user experience and reducing misconfiguration. Enhanced logging for email operations and improved UI feedback for encryption selection. --- app/Controllers/AuthController.php | 104 ++---- app/Controllers/SettingsController.php | 156 +++------ app/Helpers/EmailHelper.php | 429 +++++++++++++++++++++++++ app/Services/Channels/EmailChannel.php | 125 +++---- app/Views/settings/index.php | 61 +++- 5 files changed, 599 insertions(+), 276 deletions(-) create mode 100644 app/Helpers/EmailHelper.php diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index ea488a3..e1f5ba7 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -7,16 +7,20 @@ use App\Models\User; use App\Models\Setting; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; +use App\Helpers\EmailHelper; +use App\Services\Logger; class AuthController extends Controller { private User $userModel; private Setting $settingModel; + private Logger $logger; public function __construct() { $this->userModel = new User(); $this->settingModel = new Setting(); + $this->logger = new Logger('auth'); } /** @@ -690,43 +694,21 @@ class AuthController extends Controller */ private function sendVerificationEmail($email, $fullName, $token) { - try { - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - $verifyUrl = $appSettings['app_url'] . '/verify-email?token=' . $token; - - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = (int)$emailSettings['mail_port']; - $mail->CharSet = 'UTF-8'; - - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($email, $fullName); - - $mail->isHTML(true); - $mail->Subject = 'Verify Your Email Address'; - $mail->Body = " -

Welcome to Domain Monitor!

-

Hello {$fullName},

-

Thank you for registering. Please click the link below to verify your email address:

-

Verify Email Address

-

Or copy and paste this URL into your browser:

-

{$verifyUrl}

-

This link will expire in 24 hours.

-

If you did not create an account, please ignore this email.

- "; - - $mail->send(); - - } catch (Exception $e) { + $result = EmailHelper::sendVerificationEmail($email, $fullName, $token); + + if (!$result['success']) { // Log error but don't fail the registration - error_log('Failed to send verification email: ' . $e->getMessage()); + $this->logger->error('Failed to send verification email', [ + 'email' => $email, + 'full_name' => $fullName, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + } else { + $this->logger->info('Verification email sent successfully', [ + 'email' => $email, + 'full_name' => $fullName + ]); } } @@ -735,43 +717,21 @@ class AuthController extends Controller */ private function sendPasswordResetEmail($email, $fullName, $token) { - try { - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - $resetUrl = $appSettings['app_url'] . '/reset-password?token=' . $token; - - $mail = new PHPMailer(true); - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = (int)$emailSettings['mail_port']; - $mail->CharSet = 'UTF-8'; - - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($email, $fullName); - - $mail->isHTML(true); - $mail->Subject = 'Reset Your Password'; - $mail->Body = " -

Password Reset Request

-

Hello {$fullName},

-

We received a request to reset your password. Click the link below to create a new password:

-

Reset Password

-

Or copy and paste this URL into your browser:

-

{$resetUrl}

-

This link will expire in 1 hour.

-

If you did not request a password reset, please ignore this email and your password will remain unchanged.

- "; - - $mail->send(); - - } catch (Exception $e) { + $result = EmailHelper::sendPasswordResetEmail($email, $fullName, $token); + + if (!$result['success']) { // Log error - error_log('Failed to send password reset email: ' . $e->getMessage()); + $this->logger->error('Failed to send password reset email', [ + 'email' => $email, + 'full_name' => $fullName, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + } else { + $this->logger->info('Password reset email sent successfully', [ + 'email' => $email, + 'full_name' => $fullName + ]); } } diff --git a/app/Controllers/SettingsController.php b/app/Controllers/SettingsController.php index 4aa3172..6a436b9 100644 --- a/app/Controllers/SettingsController.php +++ b/app/Controllers/SettingsController.php @@ -5,15 +5,19 @@ namespace App\Controllers; use Core\Controller; use Core\Auth; use App\Models\Setting; +use App\Helpers\EmailHelper; +use App\Services\Logger; class SettingsController extends Controller { private Setting $settingModel; + private Logger $logger; public function __construct() { Auth::requireAdmin(); $this->settingModel = new Setting(); + $this->logger = new Logger('settings'); } public function index() @@ -250,12 +254,36 @@ class SettingsController extends Controller $this->verifyCsrf('/settings#email'); try { + $port = (int)trim($_POST['mail_port'] ?? '2525'); + $encryption = trim($_POST['mail_encryption'] ?? 'tls'); + + // Auto-detect encryption based on port if not explicitly set + $originalEncryption = $encryption; + if (empty($encryption) || $encryption === 'tls') { + if ($port === 465) { + $encryption = 'ssl'; // Port 465 should use SSL + $this->logger->info('Auto-detected SSL encryption for port 465', [ + 'port' => $port, + 'original_encryption' => $originalEncryption, + 'detected_encryption' => $encryption + ]); + } elseif ($port === 587) { + $encryption = 'tls'; // Port 587 should use TLS + $this->logger->info('Auto-detected TLS encryption for port 587', [ + 'port' => $port, + 'original_encryption' => $originalEncryption, + 'detected_encryption' => $encryption + ]); + } + // For other ports, keep the user's selection + } + $emailSettings = [ 'mail_host' => trim($_POST['mail_host'] ?? ''), - 'mail_port' => trim($_POST['mail_port'] ?? '2525'), + 'mail_port' => $port, 'mail_username' => trim($_POST['mail_username'] ?? ''), 'mail_password' => trim($_POST['mail_password'] ?? ''), - 'mail_encryption' => trim($_POST['mail_encryption'] ?? 'tls'), + 'mail_encryption' => $encryption, 'mail_from_address' => trim($_POST['mail_from_address'] ?? ''), 'mail_from_name' => trim($_POST['mail_from_name'] ?? 'Domain Monitor') ]; @@ -381,111 +409,31 @@ class SettingsController extends Controller return; } - try { - // Get current email settings - $emailSettings = $this->settingModel->getEmailSettings(); - $appSettings = $this->settingModel->getAppSettings(); - - // Create PHPMailer instance - $mail = new \PHPMailer\PHPMailer\PHPMailer(true); - - // Server settings - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = $emailSettings['mail_port']; - - // Recipients - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($testEmail); - - // Content - $mail->isHTML(true); - $mail->Subject = 'Test Email from ' . $appSettings['app_name']; + // Use EmailHelper to send test email + $result = EmailHelper::sendTestEmail($testEmail); + + if ($result['success']) { + $_SESSION['success'] = $result['message']; + $this->logger->info('Test email sent successfully', [ + 'email' => $testEmail + ]); + } else { + // Log detailed error information for debugging + $this->logger->error('Test email failed', [ + 'email' => $testEmail, + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); - $appName = htmlspecialchars($appSettings['app_name']); - $appUrl = htmlspecialchars($appSettings['app_url']); - $currentTime = date('F j, Y g:i A'); + $_SESSION['error'] = $result['message']; - $mail->Body = " - - - - - -
-
-

✅ Email Test Successful!

-
-
-
- Success! Your email configuration is working correctly. -
-

This is a test email from {$appName}.

-

If you're seeing this message, it means your SMTP settings are configured properly and emails are being delivered successfully.

- - - - - - - - - - - - - - - - - - - - - - -
SMTP Host:" . htmlspecialchars($emailSettings['mail_host']) . "
SMTP Port:" . htmlspecialchars($emailSettings['mail_port']) . "
Encryption:" . htmlspecialchars($emailSettings['mail_encryption'] ?: 'None') . "
From Address:" . htmlspecialchars($emailSettings['mail_from_address']) . "
Test Time:{$currentTime}
-
- -
- - - "; - - $mail->AltBody = "Email Test Successful!\n\n" . - "This is a test email from {$appName}.\n" . - "Your SMTP configuration is working correctly.\n\n" . - "SMTP Host: {$emailSettings['mail_host']}\n" . - "SMTP Port: {$emailSettings['mail_port']}\n" . - "From: {$emailSettings['mail_from_address']}\n" . - "Test Time: {$currentTime}"; - - $mail->send(); - - $_SESSION['success'] = "Test email sent successfully to {$testEmail}. Please check your inbox."; - $this->redirect('/settings#email'); - - } catch (\Exception $e) { - $_SESSION['error'] = "Failed to send test email: " . $e->getMessage(); - $this->redirect('/settings#email'); + // In development, show more detailed error + if (($_ENV['APP_ENV'] ?? 'production') === 'development') { + $_SESSION['error'] .= " (Debug: " . ($result['debug_info'] ?? $result['error']) . ")"; + } } + + $this->redirect('/settings#email'); } } diff --git a/app/Helpers/EmailHelper.php b/app/Helpers/EmailHelper.php new file mode 100644 index 0000000..e440af1 --- /dev/null +++ b/app/Helpers/EmailHelper.php @@ -0,0 +1,429 @@ +getEmailSettings(); + } + + /** + * Get app settings from database + */ + public static function getAppSettings(): array + { + return self::getSettingModel()->getAppSettings(); + } + + /** + * Create and configure a PHPMailer instance with proper settings + */ + public static function createMailer(): PHPMailer + { + $emailSettings = self::getEmailSettings(); + $mail = new PHPMailer(true); + + // Server settings + $mail->isSMTP(); + $mail->Host = $emailSettings['mail_host']; + $mail->SMTPAuth = !empty($emailSettings['mail_username']); + $mail->Username = $emailSettings['mail_username']; + $mail->Password = $emailSettings['mail_password']; + $mail->Port = (int)$emailSettings['mail_port']; + + // Configure encryption based on port + $port = (int)$emailSettings['mail_port']; + $encryption = $emailSettings['mail_encryption']; + + // Auto-detect encryption for common ports + if ($port === 465) { + // Port 465 typically uses SSL + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } elseif ($port === 587) { + // Port 587 typically uses TLS + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } elseif ($port === 25 || $port === 2525) { + // Port 25/2525 might use TLS or no encryption + if ($encryption === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } else { + $mail->SMTPSecure = ''; + } + } else { + // Use configured encryption for other ports + if ($encryption === 'ssl') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } elseif ($encryption === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } else { + $mail->SMTPSecure = ''; + } + } + + // Configure timeouts and SSL options + $mail->Timeout = 30; // 30 seconds total timeout + $mail->SMTPOptions = [ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true + ] + ]; + + // Set character encoding + $mail->CharSet = 'UTF-8'; + + return $mail; + } + + /** + * Send a test email + */ + public static function sendTestEmail(string $toEmail): array + { + try { + $emailSettings = self::getEmailSettings(); + $appSettings = self::getAppSettings(); + $mail = self::createMailer(); + + // Set sender + $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); + $mail->addAddress($toEmail); + + // Content + $mail->isHTML(true); + $mail->Subject = 'Test Email from ' . $appSettings['app_name']; + + $appName = htmlspecialchars($appSettings['app_name']); + $appUrl = htmlspecialchars($appSettings['app_url']); + $currentTime = date('F j, Y g:i A'); + + $mail->Body = " + + + + + +
+
+

✅ Email Test Successful!

+
+
+
+ Success! Your email configuration is working correctly. +
+

This is a test email from {$appName}.

+

If you're seeing this message, it means your SMTP settings are configured properly and emails are being delivered successfully.

+ + + + + + + + + + + + + + + + + + + + + + +
SMTP Host:" . htmlspecialchars($emailSettings['mail_host']) . "
SMTP Port:" . htmlspecialchars($emailSettings['mail_port']) . "
Encryption:" . htmlspecialchars($emailSettings['mail_encryption'] ?: 'None') . "
From Address:" . htmlspecialchars($emailSettings['mail_from_address']) . "
Test Time:{$currentTime}
+
+ +
+ + + "; + + $mail->AltBody = "Email Test Successful!\n\n" . + "This is a test email from {$appName}.\n" . + "Your SMTP configuration is working correctly.\n\n" . + "SMTP Host: {$emailSettings['mail_host']}\n" . + "SMTP Port: {$emailSettings['mail_port']}\n" . + "From: {$emailSettings['mail_from_address']}\n" . + "Test Time: {$currentTime}"; + + $mail->send(); + + return [ + 'success' => true, + 'message' => "Test email sent successfully to {$toEmail}. Please check your inbox." + ]; + + } catch (Exception $e) { + $errorMessage = "Failed to send test email: " . $e->getMessage(); + $debugInfo = $mail->ErrorInfo ?? 'No debug info available'; + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $toEmail, + 'smtp_error' => $debugInfo, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage(), + 'debug_info' => $debugInfo + ]; + } + } + + /** + * Send a notification email + */ + public static function sendNotificationEmail(string $toEmail, string $subject, string $message, array $data = []): array + { + try { + $emailSettings = self::getEmailSettings(); + $appSettings = self::getAppSettings(); + $mail = self::createMailer(); + + // Set sender + $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); + $mail->addAddress($toEmail); + + // Content + $mail->isHTML(true); + $mail->Subject = $subject; + $mail->Body = self::formatHtmlBody($message, $data, $appSettings); + $mail->AltBody = strip_tags($message); + + $mail->send(); + + return [ + 'success' => true, + 'message' => "Email sent successfully to {$toEmail}" + ]; + + } catch (Exception $e) { + $errorMessage = "Failed to send email: " . $e->getMessage(); + $debugInfo = $mail->ErrorInfo ?? 'No debug info available'; + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $toEmail, + 'subject' => $subject, + 'smtp_error' => $debugInfo, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage(), + 'debug_info' => $debugInfo + ]; + } + } + + /** + * Format HTML email body + */ + private static function formatHtmlBody(string $message, array $data, array $appSettings): string + { + $messageHtml = nl2br(htmlspecialchars($message)); + $appName = htmlspecialchars($appSettings['app_name']); + $appUrl = htmlspecialchars($appSettings['app_url']); + + // Build domain link if domain ID is available + $domainLink = ''; + if (isset($data['domain_id'])) { + $domainUrl = rtrim($appUrl, '/') . '/domains/' . $data['domain_id']; + $domainLink = "

View Domain Details

"; + } + + return " + + + + + +
+
+

🔔 {$appName} Alert

+
+
+

$messageHtml

+ $domainLink +
+ +
+ + + "; + } + + /** + * Send verification email + */ + public static function sendVerificationEmail(string $email, string $fullName, string $token): array + { + try { + $appSettings = self::getAppSettings(); + $verifyUrl = $appSettings['app_url'] . '/verify-email?token=' . $token; + + $subject = 'Verify Your Email Address'; + $message = " +

Welcome to Domain Monitor!

+

Hello {$fullName},

+

Thank you for registering. Please click the link below to verify your email address:

+

Verify Email Address

+

Or copy and paste this URL into your browser:

+

{$verifyUrl}

+

This link will expire in 24 hours.

+

If you did not create an account, please ignore this email.

+ "; + + return self::sendNotificationEmail($email, $subject, $message); + + } catch (\Exception $e) { + $errorMessage = "Failed to send verification email: " . $e->getMessage(); + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $email, + 'full_name' => $fullName, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Send password reset email + */ + public static function sendPasswordResetEmail(string $email, string $fullName, string $token): array + { + try { + $appSettings = self::getAppSettings(); + $resetUrl = $appSettings['app_url'] . '/reset-password?token=' . $token; + + $subject = 'Reset Your Password'; + $message = " +

Password Reset Request

+

Hello {$fullName},

+

We received a request to reset your password. Click the link below to create a new password:

+

Reset Password

+

Or copy and paste this URL into your browser:

+

{$resetUrl}

+

This link will expire in 1 hour.

+

If you did not request a password reset, please ignore this email and your password will remain unchanged.

+ "; + + return self::sendNotificationEmail($email, $subject, $message); + + } catch (\Exception $e) { + $errorMessage = "Failed to send password reset email: " . $e->getMessage(); + + // Log the error using the application's logger + self::getLogger()->error($errorMessage, [ + 'email' => $email, + 'full_name' => $fullName, + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + + return [ + 'success' => false, + 'message' => $errorMessage, + 'error' => $e->getMessage() + ]; + } + } + + /** + * Get email subject based on data + */ + public static function getEmailSubject(array $data): string + { + if (isset($data['domain'])) { + $daysLeft = $data['days_left']; + if ($daysLeft <= 0) { + return "🚨 URGENT: Domain {$data['domain']} has EXPIRED"; + } + if ($daysLeft == 1) { + return "⚠️ CRITICAL: Domain {$data['domain']} expires TOMORROW"; + } + return "⚠️ Domain Expiration Alert: {$data['domain']} ({$daysLeft} days)"; + } + + return "Domain Monitor Alert"; + } +} diff --git a/app/Services/Channels/EmailChannel.php b/app/Services/Channels/EmailChannel.php index f784a1d..863dc06 100644 --- a/app/Services/Channels/EmailChannel.php +++ b/app/Services/Channels/EmailChannel.php @@ -5,104 +5,53 @@ namespace App\Services\Channels; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; use App\Models\Setting; +use App\Helpers\EmailHelper; +use App\Services\Logger; class EmailChannel implements NotificationChannelInterface { + private Logger $logger; + + public function __construct() + { + $this->logger = new Logger('email_channel'); + } + public function send(array $config, string $message, array $data = []): bool { - $mail = new PHPMailer(true); - try { - // Get email settings from database - $settingModel = new Setting(); - $emailSettings = $settingModel->getEmailSettings(); - $appSettings = $settingModel->getAppSettings(); - - // Server settings - $mail->isSMTP(); - $mail->Host = $emailSettings['mail_host']; - $mail->SMTPAuth = !empty($emailSettings['mail_username']); - $mail->Username = $emailSettings['mail_username']; - $mail->Password = $emailSettings['mail_password']; - $mail->SMTPSecure = $emailSettings['mail_encryption']; - $mail->Port = $emailSettings['mail_port']; - - // Recipients - $mail->setFrom($emailSettings['mail_from_address'], $emailSettings['mail_from_name']); - $mail->addAddress($config['email']); - - // Content - $mail->isHTML(true); - $mail->Subject = $this->getSubject($data); - $mail->Body = $this->formatHtmlBody($message, $data, $appSettings); - $mail->AltBody = strip_tags($message); - - $mail->send(); + $result = EmailHelper::sendNotificationEmail( + $config['email'], + EmailHelper::getEmailSubject($data), + $message, + $data + ); + + if (!$result['success']) { + $this->logger->error("Email send failed via EmailChannel", [ + 'email' => $config['email'], + 'subject' => EmailHelper::getEmailSubject($data), + 'debug_info' => $result['debug_info'] ?? null, + 'error' => $result['error'] ?? null + ]); + return false; + } + + $this->logger->info("Email sent successfully via EmailChannel", [ + 'email' => $config['email'], + 'subject' => EmailHelper::getEmailSubject($data) + ]); + return true; - } catch (Exception $e) { - error_log("Email send failed: {$mail->ErrorInfo}"); + } catch (\Exception $e) { + $this->logger->error("Email send exception in EmailChannel", [ + 'email' => $config['email'], + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); return false; } } - private function getSubject(array $data): string - { - if (isset($data['domain'])) { - $daysLeft = $data['days_left']; - if ($daysLeft <= 0) { - return "🚨 URGENT: Domain {$data['domain']} has EXPIRED"; - } - if ($daysLeft == 1) { - return "⚠️ CRITICAL: Domain {$data['domain']} expires TOMORROW"; - } - return "⚠️ Domain Expiration Alert: {$data['domain']} ({$daysLeft} days)"; - } - - return "Domain Monitor Alert"; - } - - private function formatHtmlBody(string $message, array $data, array $appSettings): string - { - $messageHtml = nl2br(htmlspecialchars($message)); - $appName = htmlspecialchars($appSettings['app_name']); - $appUrl = htmlspecialchars($appSettings['app_url']); - - // Build domain link if domain ID is available - $domainLink = ''; - if (isset($data['domain_id'])) { - $domainUrl = rtrim($appUrl, '/') . '/domains/' . $data['domain_id']; - $domainLink = "

View Domain Details

"; - } - - return " - - - - - -
-
-

🔔 {$appName} Alert

-
-
-

$messageHtml

- $domainLink -
- -
- - - "; - } } diff --git a/app/Views/settings/index.php b/app/Views/settings/index.php index c94ed8a..1a2a478 100644 --- a/app/Views/settings/index.php +++ b/app/Views/settings/index.php @@ -203,23 +203,18 @@ foreach ($notificationPresets as $key => $preset) {
-
- -
-
-

- - Protocol: This application uses SMTP (Simple Mail Transfer Protocol) for sending emails. -

-
+

+ + Will auto-update based on port selection +

@@ -648,6 +643,48 @@ foreach ($notificationPresets as $key => $preset) {