Send mail via SMTP from your own server
Building a "Send via SMTP" Button
In our last tutorial, we created a button that opened the user's local email client. Now, we'll build a more powerful and professional solution: a button that sends an email directly from the server using SMTP. This approach is essential for automated workflows, sending transactional emails, or simply ensuring that emails are sent reliably, regardless of the user's local setup.
This button will fetch contact details, use a template for the subject and body, and send the email in the background, providing the user with a simple success or failure message.
What You'll Learn
This guide will teach you how to create a button script that:
- Securely fetches data from a primary record and a related address record.
- Uses the powerful PHPMailer library to send emails via SMTP.
- Dynamically pulls SMTP credentials from user-specific settings (Preferences).
- Provides clear JSON feedback (success or error) to the user.
- Includes robust error handling for debugging SMTP connection issues.
The Code: `send_mail_smtp.php`
This script is a pure PHP endpoint. It receives a request, connects to a mail server, sends an email, and returns a JSON response. It does not render any HTML itself. Because it's tailored to a specific data structure (a main record with a linked address), it's best to place it in a setup-specific directory like /setup_adressen/.
<?php
ob_start(); // Start Output Buffering
// --- Robust Error Handling for AJAX endpoints ---
set_error_handler(function ($severity, $message, $file, $line) {
if (error_reporting() === 0) return false;
throw new ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function ($exception) {
ob_end_clean(); // Discard any previous output
header('Content-Type: application/json');
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Internal Server Error: ' . $exception->getMessage(),
'debug' => ['file' => $exception->getFile(), 'line' => $exception->getLine()]
]);
exit();
});
// Load Composer's autoloader first to make PHPMailer available
require_once __DIR__ . '/../vendor/autoload.php';
require_once '../init.php';
require_once '../configmail.php'; // Global fallback config
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
// --- Access Control & JSON Header ---
header('Content-Type: application/json');
if (!isset($_SESSION['user'])) {
echo json_encode(['success' => false, 'message' => 'Authentication required']);
exit();
}
// --- Get and validate parameters ---
$table_id = (int)($_GET['table_id'] ?? 0);
$record_id = (int)($_GET['id'] ?? 0);
$sig_from_url = trim($_GET['sig'] ?? '');
if ($table_id === 0 || $record_id === 0) {
echo json_encode(['success' => false, 'message' => 'Table ID or Record ID is missing']);
exit();
}
// --- Security Check: Validate signature ---
$expected_sig = hash_hmac('sha256', "{$table_id}-{$record_id}", INVITE_SECRET_KEY);
if (!hash_equals($expected_sig, $sig_from_url)) {
echo json_encode(['success' => false, 'message' => 'Invalid signature.']);
exit();
}
// --- Fetch Data (Main Record, Preferences, Linked Address) ---
$table_definition = DBGet('custom_tables', $table_id);
$table_owner_team = $table_definition['team'];
$physical_table_name = "data_team_{$table_owner_team}_{$table_id}";
$main_record = DBGet($physical_table_name, $record_id);
if (!$main_record) {
echo json_encode(['success' => false, 'message' => 'Main record not found.']);
exit();
}
$user_team = $_SESSION['user']['team'];
$team_preferences_raw = DBGet('preferences', null, ['name', 'value'], 'team = ?', [$user_team]);
$team_preferences = $team_preferences_raw ? array_column($team_preferences_raw, 'value', 'name') : [];
$address_record = null;
$address_lookup_field_name = 'adresse';
if (isset($main_record[$address_lookup_field_name]) && $main_record[$address_lookup_field_name] > 0) {
$address_record_id = $main_record[$address_lookup_field_name];
$lookup_field_meta_array = DBGet('custom_fields', null, ['options'], 'custom_table_id = ? AND field_name = ?', [$table_id, $address_lookup_field_name]);
$lookup_field_meta = $lookup_field_meta_array[0] ?? null;
if ($lookup_field_meta) {
$lookup_options = json_decode($lookup_field_meta['options'], true);
$linked_table_id = $lookup_options['linked_table_id'] ?? $lookup_options['lookup_table_id'] ?? 0;
if ($linked_table_id > 0) {
$linked_table_def = DBGet('custom_tables', $linked_table_id);
if ($linked_table_def) {
$linked_physical_table = "data_team_{$linked_table_def['team']}_{$linked_table_id}";
$address_record = DBGet($linked_physical_table, (int)$address_record_id);
}
}
}
}
if (!$address_record || empty($address_record['mail'])) {
echo json_encode(['success' => false, 'message' => 'The email address could not be found in the linked address record.']);
exit();
}
// --- Placeholder replacement logic ---
$placeholder_callback = function ($matches) use ($main_record, $address_record, $team_preferences) {
$placeholder_key = $matches[1];
if (str_starts_with($placeholder_key, 'adresse.') && $address_record) {
$sub_key = substr($placeholder_key, 8);
return htmlspecialchars($address_record[$sub_key] ?? '');
}
if (isset($main_record[$placeholder_key])) {
return in_array($placeholder_key, ['text']) ? $main_record[$placeholder_key] : htmlspecialchars($main_record[$placeholder_key]);
}
if (isset($team_preferences[$placeholder_key])) {
return nl2br(htmlspecialchars($team_preferences[$placeholder_key]));
}
return $matches[0];
};
// --- Define Mail Content ---
$recipient_email = $address_record['mail'];
$recipient_name = trim(($address_record['vorname'] ?? '') . ' ' . ($address_record['nachname'] ?? ''));
$subject_template = $main_record['betreff'] ?? 'Your Inquiry';
$body_template = "<p>{adresse.anrede} {adresse.nachname},</p>" . ($main_record['text'] ?? '<p>Thank you for your message.</p>') . "<p>{team_signature}</p>";
$subject = preg_replace_callback('/{([a-zA-Z0-9_.]+)}/', $placeholder_callback, $subject_template);
$body = preg_replace_callback('/{([a-zA-Z0-9_.]+)}/', $placeholder_callback, $body_template);
// --- Configure and Send with PHPMailer ---
$mail = new PHPMailer(true);
try {
// Enable verbose debug output for troubleshooting
$mail->SMTPDebug = 0; // Set to 2 for detailed client-server communication
$mail->isSMTP();
// Check if all necessary team-specific SMTP settings are present
$use_team_smtp = isset($team_preferences['mail_smtp_host'], $team_preferences['mail_smtp_user'], $team_preferences['mail_smtp_pass'], $team_preferences['mail_smtp_port'], $team_preferences['mail_smtp_secure']);
if ($use_team_smtp) {
$mail->Host = $team_preferences['mail_smtp_host'];
$mail->Username = $team_preferences['mail_smtp_user'];
$mail->Password = $team_preferences['mail_smtp_pass'];
$mail->SMTPSecure = $team_preferences['mail_smtp_secure'];
$mail->Port = (int)$team_preferences['mail_smtp_port'];
} else {
// Fallback to global configuration from configmail.php
$mail->Host = MAIL_HOST;
$mail->Username = MAIL_USERNAME;
$mail->Password = MAIL_PASSWORD;
$mail->SMTPSecure = MAIL_ENCRYPTION;
$mail->Port = (int)MAIL_PORT;
}
$mail->SMTPAuth = true;
$mail->CharSet = 'UTF-8';
// This is often needed for shared hosting or misconfigured mail servers
$mail->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]];
// Sender and Recipient
$from_address = $team_preferences['mail_from_address'] ?? MAIL_USERNAME;
$from_name = $team_preferences['mail_from_name'] ?? lang('my_website');
$mail->setFrom($from_address, $from_name);
$mail->addAddress($recipient_email, $recipient_name);
$mail->addReplyTo($from_address, $from_name);
// Content
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $body;
$mail->send();
// Log the action
log_user_action($_SESSION['user']['id'], 'mail_sent', $table_definition['table_name'], "Email sent to {$recipient_email}. Subject: {$subject} (Record-ID: {$record_id})");
echo json_encode(['success' => true, 'message' => "Email was successfully sent to <strong>" . htmlspecialchars($recipient_email) . "</strong>."]);
} catch (Exception $e) {
$error_message = "The email could not be sent. Error: " . ($mail->ErrorInfo ?: $e->getMessage());
log_user_action($_SESSION['user']['id'], 'mail_error', $table_definition['table_name'], "Failed to send email to {$recipient_email} (Record-ID: {$record_id}): " . $error_message);
error_log("PHPMailer Error in send_mail_smtp.php: " . $error_message);
echo json_encode(['success' => false, 'message' => $error_message]);
}
ob_end_flush();
Breaking Down the Code
This script is a pure server-side action. It gathers all necessary data, connects to a mail server, and sends an email, all in the background.
Part 1: Security and Data Fetching
The script begins with our standard, robust security checks to validate the session and the request signature. It then performs a multi-step data fetch:
- Fetch Main Record: It retrieves the primary record from which the button was clicked (e.g., a "correspondence" record).
- Fetch User Preferences: It loads all settings from the
preferencestable that belong to the current user's team. This is where we'll get our SMTP credentials. - Fetch Linked Address: Just like in our previous `mailto` example, it finds the ID of the address record in the main record's `adresse` field, looks up the metadata for that field to find the correct address table, and then fetches the complete address record. This gives us the recipient's email.
Part 2: Dynamic Content with Placeholders
Before sending the email, the script prepares the content. It uses a template for the subject and body and replaces placeholders like {betreff} or {adresse.firma} with the actual data from the records we just fetched. This is a powerful way to create dynamic, personalized emails.
Part 3: Configuring and Sending with PHPMailer
This is the core of the script. It uses the popular PHPMailer library to handle the complexities of SMTP communication.
- Credential Logic: This is a key feature. The script first checks if all necessary SMTP settings (host, user, password, etc.) exist in the team's preferences. If they do, it uses them. If not, it falls back to the global credentials defined in the
configmail.phpfile. This allows each team to use their own mail server. - Connection Settings: It sets the host, port, encryption, and authentication details. The
SMTPOptionsarray is an important addition for handling common SSL certificate issues on shared hosting environments. - Sending: It sets the sender, recipient, subject, and body, and then calls
$mail->send(). The entire process is wrapped in atry...catchblock to handle any exceptions PHPMailer might throw (e.g., wrong password, connection refused). - JSON Response: Finally, it sends a JSON response back to the browser, indicating whether the email was sent successfully or not. The generic button handler in our application is built to catch this response and display the message in a dialog.
Configuration: Setting Up Your SMTP Credentials
For this script to work, it needs to know how to connect to your mail server. The best practice is to store these credentials in the application's user preferences, which are specific to your team.
Step 1: Navigate to Preferences
- Log in to the application.
- Navigate to Settings Preferences.
Step 2: Add the Required Settings
Click the "New Setting" button and create an entry for each of the following keys. The values should match the details provided by your email host (e.g., GMail, Outlook, your web host).
| Setting Key (Name) | Example Value | Description |
|---|---|---|
mail_smtp_host |
smtp.your-provider.com |
The address of your SMTP server. |
mail_smtp_user |
your-email@example.com |
Your full email address or username for the SMTP server. |
mail_smtp_pass |
YourSecretPassword |
Your email or app-specific password. |
mail_smtp_port |
465 or 587 |
The port for the SMTP connection (465 for SSL, 587 for TLS). |
mail_smtp_secure |
ssl or tls |
The encryption method required by your server. |
mail_from_address |
your-email@example.com |
The email address that will appear as the sender. |
mail_from_name |
Your Company Name |
The name that will appear as the sender. |
Once these settings are saved, the "Send via SMTP" button will automatically use them for your team.
Integrating the Button
The final step is to create the button in the system settings.
- Navigate to Settings Custom Buttons.
- Click "New Button".
- Fill out the form:
- Button Display Name:
Send via SMTP - URL / Script Path:
setup_adressen/send_mail_smtp.php - Icon:
fa-serverorfa-envelope-circle-check - Color:
btn-primary - Display Context: Select "Record View (action per record)".
- Button Display Name:
- Save the button.
That's it! Unlike our previous `mailto` example, this button does not need a special `no-ajax` class. The application's default JavaScript handler is designed to process the JSON response from this script and will automatically show a success or error dialog.
You have now created a fully functional, server-side email sending action. This pattern can be adapted for many other tasks, such as generating invoices, updating external systems, or triggering complex database operations.