Back to overview

Creating a new mail in your mail app

Scripting & Buttons Published on 31.10.2025

Building an Interactive "Send Email" Button

In our previous tutorials, we created buttons that opened external links and displayed data in a modal. Now, we'll build a button that bridges the gap between our web application and the user's desktop: a "Send Email" button. This button will fetch data from a record and its related address record, construct a pre-filled email, and open the user's default email client.

This is a powerful workflow that saves users time and reduces errors by pre-filling the recipient, subject, and body of an email directly from your application's data.

The Code: `send_mail.php`

This script is a pure PHP endpoint. It doesn't render any visible HTML itself. Instead, it processes a request, gathers data, and returns a JSON object with instructions for the client-side JavaScript. Save this file as send_mail.php inside a setup-specific directory like /setup_adressen/, as it's tailored for a specific data structure.

<?php
ob_start(); // Start Output Buffering

// --- Robust Error Handling for AJAX endpoints ---
set_exception_handler(function ($exception) {
    ob_end_clean(); // Discard any previous output
    // Use the global api_error_response function
    api_error_response(500, 'INTERNAL_SERVER_ERROR', 'Internal Server Error: ' . $exception->getMessage());
});
set_error_handler(function ($severity, $message, $file, $line) {
    if (error_reporting() === 0) return false;
    throw new ErrorException($message, 0, $severity, $file, $line);
});

require_once '../init.php';

// --- Security & Initialization ---
header('Content-Type: application/json');

if (!isset($_SESSION['user'])) {
    api_error_response(403, 'AUTH_REQUIRED', 'Authentication required.');
}

$table_id = (int)($_GET['table_id'] ?? 0);
$record_id = (int)($_GET['id'] ?? 0);
$submitted_sig = $_GET['sig'] ?? '';

if ($table_id <= 0 || $record_id <= 0) {
    api_error_response(400, 'INVALID_INPUT', 'Invalid request: Table or Record ID missing.');
}

// --- Permission Check ---
$table_definition = DBGet('custom_tables', $table_id);
$accessible_tables = get_user_accessible_tables(['id']);
if (!$table_definition || !in_array($table_id, array_column($accessible_tables, 'id')) || !hash_equals(hash_hmac('sha256', "{$table_id}-{$record_id}", INVITE_SECRET_KEY), $submitted_sig)) {
    api_error_response(403, 'FORBIDDEN', 'Access to this record is denied.');
}

// --- Fetch Record ---
$table_owner_team = $table_definition['team'];
$physical_table_name = "data_team_{$table_owner_team}_{$table_id}";
$record = DBGet($physical_table_name, $record_id);

if (!$record) {
    api_error_response(404, 'RECORD_NOT_FOUND', 'Main record not found.');
}

// --- Build Mailto Link ---

// 1. Get recipient address from the linked address table
$recipient = '';
$salutation = '';
$lastname = '';
$address_lookup_field_name = 'adresse'; // Field name in the current table that links to the address table
$address_record_id = $record[$address_lookup_field_name] ?? 0;

if (empty($address_record_id)) {
    api_error_response(404, 'ADDRESS_NOT_LINKED', "No address record is linked in the '{$address_lookup_field_name}' field.");
}

if ($address_record_id > 0) {
    // Get metadata of the 'adresse' lookup field to find the linked table
    $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) {
            // Get definition of the linked address table
            $linked_table_def = DBGet('custom_tables', $linked_table_id);
            if ($linked_table_def) {
                // Build physical table name and fetch the address record
                $linked_physical_table = "data_team_{$linked_table_def['team']}_{$linked_table_id}";
                $address_record = DBGet($linked_physical_table, (int)$address_record_id, ['mail', 'anrede', 'nachname']);
                if ($address_record && !empty($address_record['mail'])) {
                    $recipient = $address_record['mail'];
                    $salutation = $address_record['anrede'] ?? '';
                    $lastname = $address_record['nachname'] ?? '';
                }
            }
        }
    }
}

if (empty($recipient)) {
    api_error_response(404, 'RECIPIENT_NOT_FOUND', "No email address was found in the 'mail' field of the linked address record.");
}

// 2. Subject
$subject = $record['betreff'] ?? 'Inquiry regarding process #' . $record['id'];

// 3. Email Body (replace placeholders)
$body_text_from_record = strip_tags(render_snippet($record['text'] ?? '', $record, false));

// Assemble salutation if available
$full_salutation = '';
if (!empty($salutation) && !empty($lastname)) {
    $full_salutation = trim($salutation . ' ' . $lastname) . ",\n\n"; // Comma and two newlines
}

$body = $full_salutation . $body_text_from_record;

// 4. URL-encode all parts safely
$mailto_link = 'mailto:' . rawurlencode($recipient)
             . '?subject=' . rawurlencode($subject)
             . '&body=' . rawurlencode($body);

// --- Send JSON Response ---
echo json_encode([
    'success' => true,
    'mailto' => $mailto_link,
    'recipient' => $recipient
]);

ob_end_flush(); // Send the output buffer and end it

Breaking Down the Code

This script is a great example of a server-side action. It receives a request, performs complex data lookups, and returns a simple, structured response. Let's examine the key parts.

Part 1: Security and Data Fetching

The script begins with the same robust security checks as our previous examples: session validation, parameter checks, and signature verification. The interesting part comes next:

  1. Fetch Primary Record: It starts by fetching the main record (e.g., a "correspondence" entry) using the provided table_id and record_id.
  2. Identify the Link: It looks for a field named adresse in this record. This field is a "Lookup" field, and its value is the ID of a record in another table (our address table).
  3. Follow the Link: This is the advanced step. The script doesn't assume it knows where the address data is. Instead, it queries the custom_fields table to get the metadata for the `adresse` field. This metadata tells it which table is linked (the `linked_table_id`).
  4. Fetch Related Record: With the linked table ID, it can now securely build the physical table name for the addresses and fetch the correct address record using the ID it found in step 2.
  5. Extract Data: Finally, it extracts the email, salutation, and last name from the address record.

This multi-step process is extremely robust. It doesn't rely on hardcoded table names and correctly handles the application's multi-tenant data structure.

Part 2: Constructing the `mailto` Link

A mailto: link is a special URL that tells the browser to open the user's default email application. To work correctly, its components must be properly formatted and encoded.

  • Recipient: The email address is taken directly from the linked address record.
  • Subject: The subject line is taken from the `betreff` field of the main record.
  • Body: The script assembles a body text. It starts with a polite salutation (e.g., "Dear Mr. Smith,") and then appends the content from the `text` field of the main record. The strip_tags() function is used to remove any HTML, as `mailto` links only support plain text.
  • rawurlencode(): This is the most critical function here. It converts all parts of the link (especially the subject and body, which can contain spaces, special characters, and line breaks) into a URL-safe format. Without this, the link would break.

Part 3: The JSON Response and Client-Side Handling

The script's final job is to send a JSON object back to the browser. This object contains the fully constructed `mailto:` link and the recipient's email for display.

The client-side JavaScript that called this script is designed to handle this specific response. When it receives the JSON, it doesn't try to open a link itself. Instead, it uses our showDialog() function to ask the user for confirmation. The "OK" button in this dialog is special: it's rendered as an `<a>` tag with its `href` attribute set to the `mailto:` link we just built. When the user clicks it, the email client opens.

This two-step process (fetch on server, confirm on client) provides a seamless and secure user experience.

Integrating the Button

To use this script, you need to create a button and tell the application's JavaScript how to handle it.

Step 1: Create the Button in Settings

  1. Navigate to Settings Custom Buttons.
  2. Click "New Button" and fill out the form:
    • Button Display Name: Send Email
    • URL / Script Path: setup_adressen/send_mail.php (or wherever you saved the script).
    • Icon: fa-paper-plane is a great choice.
    • Color: btn-primary.
    • Display Context: Select "Record View (action per record)".
  3. Save the button.

Step 2: Tag the Button for Special Handling

Our client-side JavaScript needs to know that this button shouldn't be handled like a normal AJAX action. We do this by adding a special CSS class to the button link in our PHP view files (like `custom_table_record_edit.php`).

When rendering the button, you add the class no-ajax. The main JavaScript file (`custom_table_record_edit.js` or similar) contains a rule that looks for this class. If it finds it, it executes the special `fetch -> showDialog` logic instead of the default behavior.

<?php
// Inside your button rendering loop...
$additional_class = '';
if (str_contains($button['url'], 'send_mail.php')) {
    $additional_class = ' no-ajax';
}
?>
<a href="..." class="btn custom-action-button<?= $additional_class ?>">...</a>

With these two steps, your "Send Email" button is fully integrated and ready to use!


You've now mastered three fundamental patterns for custom buttons: client-side actions, server-side data display, and server-side data processing for client-side interaction. You're well-equipped to build almost any custom action you can imagine.