Jake Vanderwerf
2026-02-17 a24a06002081ad71a78ffeff9072725ba39cf121
inc/managers/EmailManager.php
@@ -24,7 +24,7 @@
class EmailManager
{
   private array $colours = JVB_EMAIL['colours'];
   public array $colours = JVB_EMAIL['colours'];
   private string $title = JVB_EMAIL['content']['title'];
   private string $prefix = JVB_EMAIL['content']['subjectPrefix'];
   private string $signature = JVB_EMAIL['content']['signature'];
@@ -45,23 +45,23 @@
        add_filter('wp_mail_content_type', [$this, 'setHtmlContentType']);
        // User registration emails
        add_filter('wp_new_user_notification_email', [$this, 'customizeNewUserEmail'], 10, 3);
        add_filter('wp_new_user_notification_email_admin', [$this, 'customizeNewUserEmailAdmin'], 10, 3);
        add_filter('wp_new_user_notification_email', [$this, 'customizeNewUserEmail'], 999, 3);
        add_filter('wp_new_user_notification_email_admin', [$this, 'customizeNewUserEmailAdmin'], 999, 3);
        // Password reset emails
        add_filter('retrieve_password_message', [$this, 'customizePasswordResetEmail'], 10, 4);
        add_filter('retrieve_password_title', [$this, 'customizePasswordResetTitle'], 10, 3);
        add_filter('retrieve_password_message', [$this, 'customizePasswordResetEmail'], 999, 4);
        add_filter('retrieve_password_title', [$this, 'customizePasswordResetTitle'], 999, 3);
        // User email change emails
        add_filter('email_change_email', [$this, 'customizeEmailChangeEmail'], 10, 3);
        add_filter('new_user_email_content', [$this, 'customizeNewUserEmailContent'], 10, 2);
        add_filter('email_change_email', [$this, 'customizeEmailChangeEmail'], 999, 3);
        add_filter('new_user_email_content', [$this, 'customizeNewUserEmailContent'], 999, 2);
        // Password change notification
        add_filter('password_change_email', [$this, 'customizePasswordChangeEmail'], 10, 3);
        add_filter('password_change_email', [$this, 'customizePasswordChangeEmail'], 999, 3);
        // User request/export data emails
        add_filter('user_request_action_email_content', [$this, 'customizeUserRequestEmail'], 10, 2);
        add_filter('wp_privacy_personal_data_email_content', [$this, 'customizePersonalDataEmail'], 10, 3);
        add_filter('user_request_action_email_content', [$this, 'customizeUserRequestEmail'], 999, 2);
        add_filter('wp_privacy_personal_data_email_content', [$this, 'customizePersonalDataEmail'], 999, 3);
    }
    /**
@@ -82,16 +82,16 @@
     * @param string $header Optional header text for the template
     * @return bool Whether the email was sent successfully
     */
    public function sendEmail(string $to, string $subject, string $message, string $header = ''):bool
    public function sendEmail(string $to, string $subject, string $message, string $header = '', string $preheader = '', array $headers = [], array $attachments = []):bool
    {
        // Make sure the content type is set to HTML
        add_filter('wp_mail_content_type', [$this, 'setHtmlContentType']);
        // Format the message with our template
        $formatted_message = $this->getEmailTemplate($message, $header);
        $formatted_message = $this->getEmailTemplate($message, $header, $preheader);
        // Send the email
        $result = wp_mail($to, $subject, $formatted_message);
        $result = wp_mail($to, $subject, $formatted_message, $headers, $attachments);
        // Reset content type filter to avoid affecting other emails
        remove_filter('wp_mail_content_type', [$this, 'setHtmlContentType']);
@@ -105,22 +105,55 @@
     *
     * @return string
     */
    private function getEmailTemplate(string $content, string $header = ''):string
    private function getEmailTemplate(string $content, string $headerText = '', string $preheader = ''):string
    {
        $logo = get_custom_logo();
      $custom_logo_id = get_theme_mod( 'custom_logo' );
      $logo_thumbnail = wp_get_attachment_image_src( $custom_logo_id, 'custom-logo-thumbnail' );
      $logo = ($logo_thumbnail) ? '<img src="' . esc_url( $logo_thumbnail[0] ) . '" alt="Site Logo" width="150" height="150" style="max-width:120px;height:auto;">' : '';
        // Default header if none provided
        if (empty($header)) {
            $header = $this->title;
        if (empty($headerText)) {
         $headerText = $this->title;
        }
      $preheaderHtml = '';
      if (!empty($preheader)) {
         $preheaderHtml = '
        <div style="display:none;font-size:1px;color:#fefefe;line-height:1px;font-family:Helvetica,Arial,sans-serif;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
            ' . esc_html($preheader) . '
        </div>';
      }
        return '<!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>' . $this->title . ' | ' . $this->site_name .'</title>
            <style>
        </head>
        <body style="font-family:Segoe UI, Tahoma, Geneva, Verdana, sans-serif;background-color: '.$this->colours['light'].';color:'.$this->colours['dark'].';line-height:1.5;margin:0;padding:0;">
         <div style="background-color:'.$this->colours['dark'].';color:'.$this->colours['light'].';padding:5px;text-align:center;font-size:24px;font-weight:900;letter-spacing:1.5px;text-transform:uppercase;border-radius:5px 5px 0 0;">
            <a href="' . $this->site_url . '" style="display:inline-block;vertical-align:middle;">
               ' . $logo . '
            </a>
            <p style="display:inline-block;vertical-align:middle;margin: 0 auto 0 0;">' . $headerText . '</p>
         </div>
         <div style="padding:4rem 0;width:100%;max-width:600px;margin:0 auto;">
            ' . $content . '
            '.$this->signature().'
         </div>
         <div style="border-radius:0 0 5px 5px;text-align:center;margin-top:20px;font-size:12px;background-color:'.$this->colours['light-200'].'; color: '.$this->colours['dark-200'].';padding:10px;line-height:1.6;">
            ' . $this->footer . '
         </div>
        </body>
        </html>';
    }
   private function oldStyle(){
      $oldStyle ='
      <style>
                body, table, td, p, a, li, blockquote {
                    line-height: 1.5;
                }
@@ -161,8 +194,7 @@
                    vertical-align: middle;
                }
                .header p {
                    margin: 0;
                    margin-left: auto;
                    margin: 0 auto 0 0;
                }
                .content {
@@ -194,12 +226,24 @@
                    text-align: center;
                    margin-top: 20px;
                    font-size: 12px;
                    backgorund-color: ' . $this->colours['light-200'] . ';
                    background-color: ' . $this->colours['light-200'] . ';
                    color: ' . $this->colours['dark-200'] . ';
                    padding: 10px;
                    line-height: 1.6;
                }
                .notice {
                  text-align: center;
                  border-radius: 0 8px 8px 0;
                  margin: 1rem 0;
                  border-left: 4px solid '.$this->colours['action-0'].';
                  padding: 1rem;
                  background-color: '.$this->colours['light-100'].';
                }
                .notice strong {
                  color: '.$this->colours['action-0'].';
                }
                .divider {
                    border-top: 1px solid ' . $this->colours['dark-200'] . ';
                    margin: 25px 0;
@@ -300,26 +344,8 @@
    }
}
            </style>
        </head>
        <body>
            <div class="email-container">
                <div class="header">
                    <a href="' . $this->site_url . '">
                        ' . $logo . '
                    </a>
                    <p>' . $header . '</p>
                </div>
                <div class="content">
                    ' . $content . '
                </div>
                <div class="footer">
                    ' . $this->footer . '
                </div>
            </div>
        </body>
        </html>';
    }
      ';
   }
    /**
     * New user registration email to user
@@ -336,14 +362,12 @@
         $user->user_login,
      );
      $message = apply_filters('jvbNewUserEmail', $message, $user);
      $message .= $this->signature;
      $wp_new_user_notification_email['message'] = $this->getEmailTemplate($message, JVB_EMAIL['subjects']['newUser']);
      $wp_new_user_notification_email['message'] = $this->getEmailTemplate($message, JVB_EMAIL['types']['newUser']['subject']?:'New User');
        // Change the subject line
      $prefix = JVB_EMAIL['types']['newUser']['showPrefix']??true;
      $prefix = ($prefix) ? $this->prefix : '';
        $wp_new_user_notification_email['subject'] = $prefix.JVB_EMAIL['types']['newUser']['subject'];
        $wp_new_user_notification_email['subject'] = $prefix.JVB_EMAIL['types']['newUser']['subject']?:'New User';
        return $wp_new_user_notification_email;
    }
@@ -362,7 +386,6 @@
        $message .= '<p><strong>Username:</strong> ' . $user->user_login . '</p>';
        $message .= '<p><strong>Email:</strong> ' . $user->user_email . '</p>';
      $message = apply_filters('jvbNewUserAdminEmail', $message, $user);
        $message .= $this->signature;
        $emailData['message'] = $this->getEmailTemplate($message, 'New User Registration');
        $emailData['subject'] = $this->prefix .'New ' . str_replace(BASE, '', array_values($user->roles)[0]).': '.$user->display_name;
@@ -380,7 +403,11 @@
     */
    public function customizePasswordResetEmail(string $message, string $key, string $user_login, WP_User $user):string
    {
      $reset_url = network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login');
      return $this->passwordResetEmail($user, $key);
    }
   public function passwordResetEmail(WP_User $user, string $key):string {
      $reset_url = network_site_url("login/?action=resetpass&key=$key&login=" . rawurlencode($user->user_login), 'login');
      $content = sprintf(
         '<p>Hi %s!</p>
         <p>We received a request to reset the password for an account associated with this email:</p>
@@ -390,17 +417,21 @@
         %s
         <p>Or copy and paste this link into your browser:</p>
         %s
         <div class="divider"></div>
         %s
         <p>This password reset link is only valid for 24 hours.</p>',
         $user->display_name,
         $user_login,
         jvbMailButton($reset_url,'Reset Password'),
         jvbEmailLink($reset_url)
         $user->user_login,
         $this->button($reset_url,'Reset Password'),
         $this->link($reset_url),
         $this->divider()
      );
      $content = apply_filters('jvbPasswordResetEmail', $content, $user_login, $user, $reset_url);
      $content .= $this->signature;
        return $this->getEmailTemplate($content, 'Password Reset');
    }
      return apply_filters('jvbPasswordResetEmail', $content, $user->user_login, $user, $reset_url);
   }
   public function sendPasswordResetEmail(WP_User $user, string $key):bool
   {
      return $this->sendEmail($user->user_email, $this->passwordResetTitle(), $this->passwordResetEmail($user, $key), '', 'Reset your Password');
   }
    /**
     * Customize the password reset email title
@@ -411,10 +442,14 @@
     */
    public function customizePasswordResetTitle(string $title, string $user_login, WP_User $user_data):string
    {
      return $this->passwordResetTitle();
   }
   public function passwordResetTitle():string
   {
      $prefix = JVB_EMAIL['types']['resetPass']['showPrefix']??true;
      $prefix = ($prefix) ? $this->prefix : '';
      return $prefix.JVB_EMAIL['types']['resetPass']['subject'];
      return $prefix.JVB_EMAIL['types']['resetPass']['subject']?:'Password Reset';
   }
    /**
@@ -432,20 +467,20 @@
        <p>Ideally you already know this: someone asked to change the email for your account.</p>
        <p><strong>Old Email:</strong> %s</p>
        <p><strong>New Email:</strong> %s</p>
        <div class="divider"></div>
        %s
        <p>If this is news to you, or you did not request this - please contact us immediately. You can <a href="sms:+18258239916">text us</a> or reply to this email."></a></p>
        %s',
         $newUser['first_name'],
         $oldUser['user_email'],
         $newUser['user_email'],
         jvbMailButton(wp_login_url(), 'Log In To Your Account')
         $this->divider(),
         $this->button(wp_login_url(), 'Log In To Your Account')
      );
      $content = apply_filters('jvbEmailChangeRequestEmail', $content, $oldUser, $newUser);
        $content .= $this->signature;
        $email_change_email['message'] = $this->getEmailTemplate($content, 'Email Address Changed');
      $prefix = JVB_EMAIL['types']['emailChange']['showPrefix']??true;
      $prefix = ($prefix) ? $this->prefix : '';
      $email_change_email['subject'] = $prefix.JVB_EMAIL['types']['emailChange']['subject'];
      $email_change_email['subject'] = $prefix.JVB_EMAIL['types']['emailChange']['subject']?:'Email Address Changed';
        return $email_change_email;
@@ -469,15 +504,14 @@
         %s
         <p>Or copy and paste this link into your browser:</p>
         %s',
         jvbMailButton($confirm_url, 'Confirm this Email'),
         jvbEmailLink($confirm_url)
         $this->button($confirm_url, 'Confirm this Email'),
         $this->link($confirm_url)
      );
      $content = apply_filters('jvbEmailChangedEmail', $content, $confirm_url);
        $content .= '<div class="divider"></div>';
        $content .= $this->divider();
        $content .= '<p>If you did not request this change, you can safely ignore this email and nothing will change.</p>';
        $content .= $this->signature;
        return $this->getEmailTemplate($content, 'Confirm Email Change');
    }
@@ -499,15 +533,14 @@
         <p>You can <a href="sms:+18259257398">text us</a>, or reply to this email.</p>
         %s',
         $oldUser['first_name'],
         jvbMailButton(wp_login_url(), 'Log In to Your Account')
         $this->button(wp_login_url(), 'Log In to Your Account')
      );
      $content = apply_filters('jvbPasswordChangeEmail', $content, $oldUser, $newUser);
        $content .= $this->signature;
        $pass_change_email['message'] = $this->getEmailTemplate($content, 'Password Changed');
      $prefix = JVB_EMAIL['types']['passwordChange']['showPrefix']??true;
      $prefix = ($prefix) ? $this->prefix : '';
      $pass_change_email['subject'] = $prefix.JVB_EMAIL['types']['passwordChange']['subject'];
      $pass_change_email['subject'] = $prefix.JVB_EMAIL['types']['passwordChange']['subject']?:'Password Changed';
        return $pass_change_email;
    }
@@ -545,14 +578,13 @@
         <p>Or copy and paste this link into your browser:</p>
         %s',
         $request_name,
         jvbMailButton($confirm_url, 'Confirm'),
         jvbEmailLink($confirm_url)
         $this->button($confirm_url, 'Confirm'),
         $this->link($confirm_url)
      );
      $message = apply_filters('jvbPersonalDataExport', $message, $request_type, $confirm_url, $email_data);
        $message .= '<div class="divider"></div>';
        $message .= $this->divider();
        $message .= '<p>If you did not make this request, you can safely ignore this email.</p>';
        $message .= $this->signature;
        return $this->getEmailTemplate($message, 'Action Confirmation');
    }
@@ -577,17 +609,294 @@
         %s
         <p>Or you can copy and paste this link into your browser:</p>
         %s
         <div class="divider"></div>
         %s
         <p><strong>Important:</strong> For privacy and security, this link will expire at %s.</p>',
         jvbMailButton($download_url, 'Download Your Data'),
         jvbEmailLink($download_url),
         $this->button($download_url, 'Download Your Data'),
         $this->link($download_url),
         $this->divider(),
         $expiresAt
      );
      $message = apply_filters('jvbPersonalDataExported', $message, $download_url, $expiresAt, $email_data);
        $message .= $this->signature;
        return $this->getEmailTemplate($message, 'Your Personal Data Export');
    }
   public function signature():string
   {
      return '<p><i>'.$this->signature.'</i></p>';
   }
   public function button(string $link, string $title):string
   {
      return sprintf(
         '<p style="text-align: center;"><a href="%s" style="display:inline-block;padding:16px 10px;border-radius:4px;background-color:%s;color:%s;text-decoration:none;font-weight:bold;margin:15px 0;letter-spacing:1px;text-transform:uppercase;">%s</a></p>',
         $link,
         $this->colours['action-0'],
         $this->colours['action-contrast'],
         $title
      );
   }
   public function link(string $link):string
   {
      return sprintf(
         '<code style="color:%s;border:1px solid %s;background-color:%s;border-radius:4px;user-select:all;">%s</code>',
         $this->colours['dark-200'],
         $this->colours['dark-100'],
         $this->colours['light-100'],
         $link
      );
   }
   public function divider():string
   {
      return '<div style="border-top:1px solid '.$this->colours['dark-200'].';margin:25px 0;"></div>';
   }
   public function notice(string $text):string
   {
      return '<div style="border-radius: 0 8px 8px 0; margin: 1rem 0; border-left: 4px solid '.$this->colours['action-0'].';padding:1rem;background-color:'.$this->colours['light-100'].';">
      '.str_replace('<strong>', '<strong style="color:'.$this->colours['action-0'].'">',$text).'
      </div>';
   }
   public function callout(string $text):string
   {
      return sprintf(
         '<div style="padding:2rem;margin:2rem 3rem;background-color:%s;color:%s;">%s</div>',
         $this->colours['action-0'],
         $this->colours['action-contrast'],
         str_replace('<a', '<a style="background-color:'.$this->colours['action-contrast'].';padding: 0 .125rem;border-radius:4px;"', $text)
      );
   }
   public function table(array $summarize, string $title = '', array $actions = []):string
   {
      if (empty($summarize)){
         return '';
      }
      if (!empty($title)) {
         $title = sprintf(
         '<h2 style="color:%s;border-bottom:2px solid %s;padding-bottom:15px;margin-bottom:20px;text-align:center;">%s</h2>',
         $this->colours['dark-200'],
         $this->colours['dark-200'],
         $title
         );
      }
      $content = '';
      foreach ($summarize as $index=> $item) {
         if (!array_key_exists('label', $item) && !array_key_exists('value', $item)) {
            continue;
         }
         $content .= sprintf(
            '<div style="padding:10px 0;border-bottom:1px solid %s;background-color:%s;">
        <span style="display:inline-block;vertical-align:top;font-weight:600;color:%s;width:%s;">%s</span>
        <div style="display:inline-block;vertical-align:top;width:%s;">%s</div>
    </div>',
            $this->colours['dark-200'],
            ($index%2 === 0) ? $this->colours['light-100'] : $this->colours['light-50'],
            $this->colours['dark-200'],
            '30%',  // Changed from 19% to 30%
            $item['label'],
            '68%',  // Changed from 80% to 68% (30% + 68% = 98%, leaving 2% for spacing)
            $item['value']
         );
      }
      return sprintf(
         '<div style="max-width:500px;margin:40px auto;padding:30px;background-color:%s;border-radius:10px;border:2px dashed %s;">%s%s</div>',
         $this->colours['light-100'],
         $this->colours['dark-200'],
         $title,
         $content
      );
   }
   public function card(string $content, string $title = ''):string
   {
      $titleHtml = '';
      if (!empty($title)) {
         $titleHtml = sprintf(
            '<h3 style="margin:0 0 15px 0;color:%s;font-size:18px;font-weight:600;">%s</h3>',
            $this->colours['dark'],
            esc_html($title)
         );
      }
      return sprintf(
         '<div style="background-color:%s;border:1px solid %s;border-radius:8px;padding:20px;margin:15px 0;">%s%s</div>',
         $this->colours['light-50'],
         $this->colours['light-200'],
         $titleHtml,
         $content
      );
   }
   public function stat(string $number, string $label, string $description = ''):string
   {
      $desc = $description ? sprintf(
         '<p style="margin:5px 0 0 0;font-size:12px;color:%s;">%s</p>',
         $this->colours['dark-200'],
         esc_html($description)
      ) : '';
      return sprintf(
         '<div style="text-align:center;padding:20px;background-color:%s;border-radius:8px;margin:10px 0;">
            <div style="font-size:36px;font-weight:700;color:%s;margin:0 0 5px 0;">%s</div>
            <div style="font-size:14px;font-weight:600;color:%s;text-transform:uppercase;letter-spacing:1px;">%s</div>
            %s
        </div>',
         $this->colours['light-100'],
         $this->colours['action-0'],
         esc_html($number),
         $this->colours['dark-200'],
         esc_html($label),
         $desc
      );
   }
   public function grid(array $items, int $columns = 2):string
   {
      $width = floor(100 / $columns) - 2; // 2% gap
      $html = '<div style="display:table;width:100%;margin:20px 0;">';
      foreach ($items as $index => $item) {
         if ($index > 0 && $index % $columns === 0) {
            $html .= '</div><div style="display:table;width:100%;margin:20px 0;">';
         }
         $html .= sprintf(
            '<div style="display:table-cell;width:%d%%;padding:10px;vertical-align:top;">%s</div>',
            $width,
            $item
         );
      }
      $html .= '</div>';
      return $html;
   }
   public function image(string $src, string $alt = '', int $maxWidth = 600):string
   {
      return sprintf(
         '<div style="text-align:center;margin:20px 0;">
            <img src="%s" alt="%s" style="max-width:%dpx;width:100%%;height:auto;border-radius:8px;" />
        </div>',
         esc_url($src),
         esc_attr($alt),
         $maxWidth
      );
   }
   public function h1(string $text):string
   {
      return sprintf(
         '<h1 style="color:%s;font-size:24px;font-weight:700;margin:20px 0 10px 0;">%s</h1>',
         $this->colours['dark'],
         $text
      );
   }
   public function h2(string $text):string
   {
      return sprintf(
         '<h2 style="color:%s;font-size:20px;font-weight:600;margin:20px 0 10px 0;border-bottom:2px solid %s;padding-bottom:10px;">%s</h2>',
         $this->colours['dark'],
         $this->colours['dark-200'],
         $text
      );
   }
   public function h3(string $text):string
   {
      return sprintf(
         '<h3 style="color:%s;font-size:18px;font-weight:600;margin:15px 0 10px 0;">%s</h3>',
         $this->colours['dark-200'],
         $text
      );
   }
   public function list(array $items, bool $ordered = false):string
   {
      $tag = $ordered ? 'ol' : 'ul';
      $style = $ordered
         ? 'list-style-type:decimal;padding-left:20px;margin:10px 0;'
         : 'list-style-type:disc;padding-left:20px;margin:10px 0;';
      $html = sprintf('<%s style="%s">', $tag, $style);
      foreach ($items as $item) {
         $html .= sprintf(
            '<li style="margin:5px 0;line-height:1.6;">%s</li>',
            $item
         );
      }
      $html .= sprintf('</%s>', $tag);
      return $html;
   }
   public function codeBlock(string $code, string $language = ''):string
   {
      return sprintf(
         '<pre style="background-color:%s;border:1px solid %s;border-radius:4px;padding:15px;overflow-x:auto;font-family:\'Courier New\',monospace;font-size:13px;line-height:1.4;"><code>%s</code></pre>',
         $this->colours['light-100'],
         $this->colours['dark-200'],
         esc_html($code)
      );
   }
   public function badge(string $text, string $type = 'default'):string
   {
      $colors = [
         'success' => ['bg' => '#d4edda', 'text' => '#155724'],
         'warning' => ['bg' => '#fff3cd', 'text' => '#856404'],
         'error' => ['bg' => '#f8d7da', 'text' => '#721c24'],
         'info' => ['bg' => '#d1ecf1', 'text' => '#0c5460'],
         'default' => ['bg' => $this->colours['light-200'], 'text' => $this->colours['dark-200']]
      ];
      $color = $colors[$type] ?? $colors['default'];
      return sprintf(
         '<span style="display:inline-block;padding:4px 12px;border-radius:12px;background-color:%s;color:%s;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;">%s</span>',
         $color['bg'],
         $color['text'],
         esc_html($text)
      );
   }
   public function alert(string $text, string $type = 'info'):string
   {
      $configs = [
         'success' => ['bg' => '#d4edda', 'border' => '#28a745', 'text' => '#155724', 'icon' => '✓'],
         'warning' => ['bg' => '#fff3cd', 'border' => '#ffc107', 'text' => '#856404', 'icon' => '⚠'],
         'error' => ['bg' => '#f8d7da', 'border' => '#dc3545', 'text' => '#721c24', 'icon' => '✕'],
         'info' => ['bg' => '#d1ecf1', 'border' => $this->colours['action-0'], 'text' => '#0c5460', 'icon' => 'ℹ']
      ];
      $config = $configs[$type] ?? $configs['info'];
      return sprintf(
         '<div style="border-radius:8px;margin:1rem 0;border-left:4px solid %s;padding:1rem;background-color:%s;color:%s;">
            <strong style="font-size:16px;">%s</strong> %s
        </div>',
         $config['border'],
         $config['bg'],
         $config['text'],
         $config['icon'],
         $text
      );
   }
   public function spacer(int $height = 20):string
   {
      return sprintf('<div style="height:%dpx;"></div>', $height);
   }
   public function prefix():string
   {
      return $this->prefix;
   }
}
new EmailManager();