loadSettings(); $this->loadTemplates(); $this->initHooks(); } /** * Initialize hooks */ private function initHooks(): void { // Invoice/quote send-email AJAX is handled exclusively by EasyInvoice\Admin\EasyInvoiceAjax // (published-document checks, single handler) to avoid duplicate nopriv callbacks. // Add email settings to admin add_action('admin_init', [$this, 'registerEmailSettings']); add_filter('easy_invoice_settings_sections', [$this, 'addEmailSettingsSection']); // Refresh settings when they're updated add_action('update_option_easy_invoice_email_from_name', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_from_address', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_reply_to', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_reply_to_name', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_enable_email_styling', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_logo', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_footer_text', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_bcc_admin', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_admin_email', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_subject', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_email_body', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_quote_subject', [$this, 'refreshSettings']); add_action('update_option_easy_invoice_quote_body', [$this, 'refreshSettings']); // Add email logs add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3); add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3); // Listen for payment completion to send admin notifications add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3); } /** * Load email settings */ private function loadSettings(): void { $this->settings = [ 'from_name' => get_option('easy_invoice_email_from_name', get_bloginfo('name')), 'from_email' => get_option('easy_invoice_email_from_address', get_bloginfo('admin_email')), 'reply_to_email' => get_option('easy_invoice_email_reply_to', ''), 'reply_to_name' => get_option('easy_invoice_email_reply_to_name', ''), 'enable_html' => get_option('easy_invoice_enable_email_styling', 'yes'), 'email_logo' => get_option('easy_invoice_email_logo', ''), 'footer_text' => get_option('easy_invoice_email_footer_text', ''), 'bcc_admin' => get_option('easy_invoice_bcc_admin', 'no'), 'admin_email' => get_option('easy_invoice_admin_email', get_option('admin_email')), ]; } /** * Load email templates */ private function loadTemplates(): void { $this->templates = [ 'invoice_new' => [ 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')), 'body' => get_option('easy_invoice_invoice_email_body', $this->getDefaultInvoiceTemplate()), 'type' => 'invoice' ], 'invoice_reminder' => [ 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_reminder_subject', 'Payment Reminder - Invoice #{{invoice_number}}'), 'body' => get_option('easy_invoice_reminder_body', $this->getDefaultReminderTemplate()), 'type' => 'invoice' ], 'invoice_paid' => [ 'enabled' => get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')), 'body' => get_option('easy_invoice_payment_email_body', $this->getDefaultPaymentTemplate()), 'type' => 'invoice' ], 'quote_new' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')), 'body' => get_option('easy_invoice_quote_email_body', $this->getDefaultQuoteTemplate()), 'type' => 'quote' ], 'quote_accepted' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_accepted_subject', 'Quote Accepted - #{{quote_number}}'), 'body' => get_option('easy_invoice_quote_accepted_body', $this->getDefaultQuoteAcceptedTemplate()), 'type' => 'quote' ], 'quote_declined' => [ 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes', 'subject' => get_option('easy_invoice_quote_declined_subject', 'Quote Declined - #{{quote_number}}'), 'body' => get_option('easy_invoice_quote_declined_body', $this->getDefaultQuoteDeclinedTemplate()), 'type' => 'quote' ] ]; } /** * Send invoice email * * @param Invoice $invoice The invoice * @param string $template_type Template type (new, reminder, paid) * @param array $additional_data Additional data for template * @return array Result array with success status and message */ public function sendInvoiceEmail(Invoice $invoice, string $template_type = 'new', array $additional_data = []): array { try { // Validate invoice if (!$invoice || !$invoice->getId()) { return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; } // Get client email $client_email = $invoice->getCustomerEmail(); if (empty($client_email)) { return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')]; } // Get template $template_key = 'invoice_' . $template_type; if (!isset($this->templates[$template_key])) { return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')]; } $template = $this->templates[$template_key]; // Check if email is enabled if (!$template['enabled']) { return ['success' => false, 'message' => __('Invoice available email is disabled', 'easy-invoice')]; } // Prepare email data $email_data = $this->prepareInvoiceEmailData($invoice, $template, $additional_data); // Send email $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); if ($sent) { // Log success do_action('easy_invoice_email_sent', $invoice, $client_email, $template_type); return [ 'success' => true, 'message' => __('Email sent successfully', 'easy-invoice'), 'email_data' => $email_data ]; } else { // Log failure do_action('easy_invoice_email_failed', $invoice, $client_email, $template_type); return ['success' => false, 'message' => __('Failed to send email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Email sending error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending email: ', 'easy-invoice') . $e->getMessage()]; } } /** * Send quote email * * @param Quote $quote The quote * @param string $template_type Template type (new, accepted, declined) * @param array $additional_data Additional data for template * @return array Result array with success status and message */ public function sendQuoteEmail(Quote $quote, string $template_type = 'new', array $additional_data = []): array { try { // Validate quote if (!$quote || !$quote->getId()) { return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')]; } // Get client email $client_email = $quote->getCustomerEmail(); if (empty($client_email)) { return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')]; } // Get template $template_key = 'quote_' . $template_type; if (!isset($this->templates[$template_key])) { return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')]; } $template = $this->templates[$template_key]; // Check if email is enabled if (!$template['enabled']) { return ['success' => false, 'message' => __('Quote available email is disabled', 'easy-invoice')]; } // Prepare email data $email_data = $this->prepareQuoteEmailData($quote, $template, $additional_data); // Send email $sent = $this->sendEmail( $email_data['to'], $email_data['subject'], $email_data['message'], $email_data['headers'] ); if ($sent) { // Log success do_action('easy_invoice_quote_email_sent', $quote, $client_email, $template_type); return [ 'success' => true, 'message' => __('Quote email sent successfully', 'easy-invoice'), 'email_data' => $email_data ]; } else { // Log failure do_action('easy_invoice_quote_email_failed', $quote, $client_email, $template_type); return ['success' => false, 'message' => __('Failed to send quote email', 'easy-invoice')]; } } catch (\Exception $e) { $this->log('Quote email sending error: ' . $e->getMessage(), 'error'); return ['success' => false, 'message' => __('Error sending quote email: ', 'easy-invoice') . $e->getMessage()]; } } /** * Prepare invoice email data * * @param Invoice $invoice The invoice * @param array $template The email template * @param array $additional_data Additional data * @return array Email data */ private function prepareInvoiceEmailData(Invoice $invoice, array $template, array $additional_data = []): array { // Get replacements $replacements = $this->getInvoiceReplacements($invoice, $additional_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); $message = do_shortcode($message); // Render shortcodes like [easy_invoice_url ...] // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $invoice->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Prepare quote email data * * @param Quote $quote The quote * @param array $template The email template * @param array $additional_data Additional data * @return array Email data */ private function prepareQuoteEmailData(Quote $quote, array $template, array $additional_data = []): array { // Get replacements $replacements = $this->getQuoteReplacements($quote, $additional_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $quote->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Prepare payment email data * * @param Invoice $invoice The invoice * @param array $template The email template * @param array $payment_data Payment data * @return array Email data */ private function preparePaymentEmailData(Invoice $invoice, array $template, array $payment_data = []): array { // Get replacements $replacements = $this->getPaymentReplacements($invoice, $payment_data); // Process template $subject = $this->processTemplate($template['subject'], $replacements); $message = $this->processTemplate($template['body'], $replacements); // Add HTML wrapper if enabled if ($this->settings['enable_html'] === 'yes') { $message = $this->wrapInHtmlTemplate($message); } // Prepare headers $headers = $this->prepareEmailHeaders(); // Add BCC to admin if enabled if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { $headers[] = 'Bcc: ' . $this->settings['admin_email']; } return [ 'to' => $invoice->getCustomerEmail(), 'subject' => $subject, 'message' => $message, 'headers' => $headers ]; } /** * Get invoice replacements * * @param Invoice $invoice The invoice * @param array $additional_data Additional data * @return array Replacements */ private function getInvoiceReplacements(Invoice $invoice, array $additional_data = []): array { $currency_symbol = get_option('easy_invoice_currency_symbol', '$'); // Secure link support $invoice_url = get_permalink($invoice->getId()); $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes'; if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) { $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId()); if ($secure_url) { $invoice_url = $secure_url; } } // SECURITY: attach a per-invoice access token to the outbound URL // so the legitimate email recipient can submit manual payments // without needing to log in. The token is verified server-side in // PaymentController::submitManualPayment via // InvoiceController::canSubmitPaymentForInvoice. Empty-token // guard so a CSPRNG failure doesn't produce malformed `?ik=` URLs. $invoice_access_token = \EasyInvoice\Controllers\InvoiceController::invoiceAccessToken((int) $invoice->getId()); if ($invoice_access_token !== '' && $invoice_url) { $invoice_url = add_query_arg('ik', $invoice_access_token, $invoice_url); } // Get client data for additional fields $client = null; if ($invoice->getClientId()) { $client_repository = new \EasyInvoice\Repositories\ClientRepository(); $client = $client_repository->find($invoice->getClientId()); } return array_merge([ '{{invoice_number}}' => $invoice->getNumber(), '{{invoice_title}}' => $invoice->getTitle(), '{{client_name}}' => $invoice->getCustomerName(), '{{client_email}}' => $invoice->getCustomerEmail(), '{{client_address}}' => $invoice->getCustomerAddress(), '{{client_first_name}}' => $client ? $client->getFirstName() : '', '{{client_last_name}}' => $client ? $client->getLastName() : '', '{{company_name}}' => get_bloginfo('name'), '{{company_email}}' => $this->settings['from_email'], '{{company_phone}}' => get_option('easy_invoice_company_phone', ''), '{{company_address}}' => get_option('easy_invoice_company_address', ''), '{{company_website}}' => get_option('easy_invoice_company_website', ''), '{{total_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTotal()), '{{subtotal}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getSubtotal()), '{{tax_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTaxAmount()), '{{discount_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getDiscountAmount()), '{{due_date}}' => date('F j, Y', strtotime($invoice->getDueDate())), '{{issue_date}}' => date('F j, Y', strtotime($invoice->getIssueDate())), '{{invoice_url}}' => $invoice_url, // Use correct (possibly secure) link '{{payment_url}}' => add_query_arg('payment', '1', get_permalink($invoice->getId())), '{{site_url}}' => get_site_url(), '{{admin_url}}' => admin_url(), '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')), ], $additional_data); } /** * Get quote replacements * * @param Quote $quote The quote * @param array $additional_data Additional data * @return array Replacements */ private function getQuoteReplacements(Quote $quote, array $additional_data = []): array { $currency_symbol = get_option('easy_invoice_currency_symbol', '$'); $quote_url = get_permalink($quote->getId()); $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes'; if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) { $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId()); if ($secure_url) { $quote_url = $secure_url; } } // SECURITY (CVE-2026-9021): attach the per-quote access token so // the emailed recipient lands on a page that renders the // Accept/Decline UI and can submit either action without // authenticating. Without the token the public single-quote page // is read-only (no buttons, no nonce in DOM). Lazily generates // the token on first send. The query parameter name is // intentionally short ('qk') and opaque โ leaking it via referer // headers is no worse than leaking the secure-link signature. $quote_access_token = \EasyInvoice\Controllers\QuoteController::quoteAccessToken((int) $quote->getId()); if ($quote_access_token !== '' && $quote_url) { $quote_url = add_query_arg('qk', $quote_access_token, $quote_url); } // Get client data for additional fields $client = null; if ($quote->getClientId()) { $client_repository = new \EasyInvoice\Repositories\ClientRepository(); $client = $client_repository->find($quote->getClientId()); } return array_merge([ '{{quote_number}}' => $quote->getNumber(), '{{quote_title}}' => $quote->getTitle(), '{{client_name}}' => $quote->getCustomerName(), '{{client_email}}' => $quote->getCustomerEmail(), '{{client_address}}' => $quote->getCustomerAddress(), '{{client_first_name}}' => $client ? $client->getFirstName() : '', '{{client_last_name}}' => $client ? $client->getLastName() : '', '{{company_name}}' => get_bloginfo('name'), '{{company_email}}' => $this->settings['from_email'], '{{company_phone}}' => get_option('easy_invoice_company_phone', ''), '{{company_address}}' => get_option('easy_invoice_company_address', ''), '{{company_website}}' => get_option('easy_invoice_company_website', ''), '{{total_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTotal()), '{{subtotal}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getSubtotal()), '{{tax_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTaxAmount()), '{{discount_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getDiscountAmount()), '{{expiry_date}}' => date('F j, Y', strtotime($quote->getExpiryDate())), '{{issue_date}}' => date('F j, Y', strtotime($quote->getIssueDate())), '{{quote_url}}' => $quote_url, '{{site_url}}' => get_site_url(), '{{admin_url}}' => admin_url(), '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')), ], $additional_data); } /** * Get payment replacements * * @param Invoice $invoice The invoice * @param array $payment_data Payment data * @return array Replacements */ private function getPaymentReplacements(Invoice $invoice, array $payment_data = []): array { $replacements = $this->getInvoiceReplacements($invoice, $payment_data); // Add payment-specific replacements $replacements['{{payment_amount}}'] = isset($payment_data['amount']) ? $this->formatCurrency($payment_data['amount']) : $this->formatCurrency($invoice->getTotal()); $replacements['{{payment_date}}'] = isset($payment_data['date']) ? $payment_data['date'] : current_time('Y-m-d'); $replacements['{{payment_method}}'] = isset($payment_data['method']) ? $payment_data['method'] : __('Online Payment', 'easy-invoice'); $replacements['{{transaction_id}}'] = isset($payment_data['transaction_id']) ? $payment_data['transaction_id'] : __('N/A', 'easy-invoice'); $replacements['{{acceptance_date}}'] = isset($payment_data['acceptance_date']) ? $payment_data['acceptance_date'] : current_time('Y-m-d'); $replacements['{{response_date}}'] = isset($payment_data['response_date']) ? $payment_data['response_date'] : current_time('Y-m-d'); $replacements['{{decline_reason}}'] = isset($payment_data['decline_reason']) ? $payment_data['decline_reason'] : __('No specific reason provided', 'easy-invoice'); return $replacements; } /** * Process template with replacements * * @param string $template The template * @param array $replacements The replacements * @return string Processed template */ private function processTemplate(string $template, array $replacements): string { return easy_invoice_str_replace(array_keys($replacements), array_values($replacements), $template); } /** * Prepare email headers * * @return array Headers */ private function prepareEmailHeaders(): array { $headers = [ 'Content-Type: text/html; charset=UTF-8', 'From: ' . $this->settings['from_name'] . ' <' . $this->settings['from_email'] . '>', ]; // Add Reply-To if set if (!empty($this->settings['reply_to_email'])) { $reply_to_name = !empty($this->settings['reply_to_name']) ? $this->settings['reply_to_name'] : $this->settings['from_name']; $headers[] = 'Reply-To: ' . $reply_to_name . ' <' . $this->settings['reply_to_email'] . '>'; } return $headers; } /** * Wrap message in HTML template * * @param string $message The message * @return string HTML wrapped message */ private function wrapInHtmlTemplate(string $message): string { $logo_html = ''; if (!empty($this->settings['email_logo'])) { $logo_html = '
' . __('Configure how emails are sent from Easy Invoice.', 'easy-invoice') . '
'; } /** * Text field callback * * @param array $args Field arguments */ public function textFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; } /** * Email field callback * * @param array $args Field arguments */ public function emailFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; } /** * Checkbox field callback * * @param array $args Field arguments */ public function checkboxFieldCallback(array $args): void { $field_id = $args['label_for']; $value = get_option($field_id, ''); echo ''; echo '' . __('Enable this option', 'easy-invoice') . ''; } /** * Log email sent * * @param Invoice $invoice The invoice * @param string $email The email address * @param string $type The email type */ public function logEmailSent($invoice, string $email, string $type): void { $this->log(sprintf('Email sent to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'info'); } /** * Log email failed * * @param Invoice $invoice The invoice * @param string $email The email address * @param string $type The email type */ public function logEmailFailed($invoice, string $email, string $type): void { $this->log(sprintf('Email failed to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'error'); } /** * Get default invoice template * * @return string Template */ private function getDefaultInvoiceTemplate(): string { return 'Dear {{client_name}},
Invoice #{{invoice_number}}
{{total_amount}}
Due Date: {{due_date}}
Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment or visit the link below.
๐ Payment Details:
โข Invoice Number: {{invoice_number}}
โข Total Amount: {{total_amount}}
โข Due Date: {{due_date}}
โข Payment Terms: {{payment_terms}}
๐ View Invoice Online:
{{invoice_url}}
Shortcode: [easy_invoice_url number="{{invoice_number}}" text="View Invoice"]
โ ๏ธ Important: Please ensure payment is received by the due date to avoid any late fees or service interruptions.
If you have any questions about this invoice, please do not hesitate to contact us.
Thank you for your business!
Best regards,
{{company_name}}
{{company_email}}
Dear {{client_name}},
Invoice #{{invoice_number}}
{{total_amount}}
Due Date: {{due_date}}
This is a friendly reminder that payment for the above invoice is now due. If you have already made the payment, please disregard this message.
๐ณ Payment Options:
โข Online payment through our secure portal
โข Bank transfer to the details provided
โข Check or money order
๐ View Invoice Online:
{{invoice_url}}
Shortcode: [easy_invoice_url number="{{invoice_number}}" text="View Invoice"]
๐ Need Help? If you have any questions or need to discuss payment arrangements, please contact us immediately.
Thank you for your prompt attention to this matter.
Best regards,
{{company_name}}
{{company_email}}
Dear {{client_name}},
Payment Confirmation
Invoice #{{invoice_number}}
{{payment_amount}}
Payment Date: {{payment_date}}
Payment Method: {{payment_method}}
We have successfully received your payment. Thank you for your prompt payment!
๐ Payment Details:
โข Invoice Number: {{invoice_number}}
โข Amount Paid: {{payment_amount}}
โข Payment Date: {{payment_date}}
โข Payment Method: {{payment_method}}
โข Transaction ID: {{transaction_id}}
๐ Status: PAID
Your payment has been processed and your account is now up to date. We appreciate your business!
If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.
Thank you for choosing our services!
Best regards,
{{company_name}}
{{company_email}}
Dear {{client_name}},
Quote #{{quote_number}}
{{total_amount}}
Valid Until: {{expiry_date}}
We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.
๐ Quote Summary:
โข Quote Number: {{quote_number}}
โข Total Amount: {{total_amount}}
โข Valid Until: {{expiry_date}}
โข Terms: {{payment_terms}}
๐ View Quote Online:
{{quote_url}}
Shortcode: [easy_quote_url number="{{quote_number}}" text="View Quote"]
โฐ Time Sensitive: This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.
If you have any questions or would like to discuss any aspects of this quote, please contact us.
We look forward to working with you!
Best regards,
{{company_name}}
{{company_email}}
Dear {{client_name}},
Quote #{{quote_number}} - ACCEPTED
{{total_amount}}
Acceptance Date: {{acceptance_date}}
Thank you for accepting our quote! We\'re excited to begin working on your project.
๐ Next Steps:
โข We will create an invoice for the accepted quote
โข You will receive payment instructions
โข Project work will begin as scheduled
๐ What\'s Next? Our team will be in touch shortly with the next steps and any additional information you may need.
Thank you for choosing our services!
Best regards,
{{company_name}}
{{company_email}}
Dear {{client_name}},
Quote #{{quote_number}} - DECLINED
Response Date: {{response_date}}
We have received your response regarding our quote. We understand that this quote may not have met your current needs.
๐ Feedback:
โข Reason: {{decline_reason}}
โข Response Date: {{response_date}}
๐ค Future Opportunities: We appreciate you taking the time to review our proposal. If your requirements change in the future, we would be happy to discuss new opportunities.
Thank you for considering our services!
Best regards,
{{company_name}}
{{company_email}}
Hello!
โ
Test Email Successfully Sent
Date: ' . current_time('Y-m-d H:i:s') . '
To: ' . esc_html($to_email) . '
This is a test email to verify that your Easy Invoice email configuration is working correctly.
โ๏ธ Email Settings Verified:
โข From Name: ' . esc_html($this->settings['from_name']) . '
โข From Email: ' . esc_html($this->settings['from_email']) . '
โข Reply-To: ' . esc_html($this->settings['reply_to_email'] ?: 'Not set') . '
โข HTML Emails: ' . ($this->settings['enable_html'] === 'yes' ? 'Enabled' : 'Disabled') . '
๐ Congratulations! If you received this email, your email configuration is working properly and you can now send invoices, quotes, and payment confirmations to your clients.
Thank you for using Easy Invoice!
Best regards,
' . esc_html($this->settings['from_name']) . '