mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
Switch email to CyberMail API; integrations page manages API key via DB
This commit is contained in:
+186
-239
@@ -1,369 +1,316 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - SendGrid Email Service
|
||||
*
|
||||
* Handles all transactional emails using SendGrid API
|
||||
* Tom's Java Jive - CyberMail API Email Service
|
||||
* Endpoint: POST https://platform.cyberpersons.com/email/v1/send
|
||||
*/
|
||||
|
||||
class SendGridEmail {
|
||||
class Email {
|
||||
private string $apiKey;
|
||||
private string $fromEmail;
|
||||
private string $fromName;
|
||||
|
||||
private string $endpoint = 'https://platform.cyberpersons.com/email/v1/send';
|
||||
|
||||
public function __construct() {
|
||||
// Load from settings or config
|
||||
$this->apiKey = getSetting('sendgrid_api_key', defined('SENDGRID_API_KEY') ? SENDGRID_API_KEY : '');
|
||||
$this->fromEmail = getSetting('sendgrid_from_email', defined('SENDER_EMAIL') ? SENDER_EMAIL : 'noreply@tomsjavajive.com');
|
||||
$this->fromName = getSetting('sendgrid_from_name', defined('SENDER_NAME') ? SENDER_NAME : "Tom's Java Jive");
|
||||
$this->apiKey = getSetting('cybermail_api_key', defined('CYBERMAIL_API_KEY') ? CYBERMAIL_API_KEY : '');
|
||||
$this->fromEmail = defined('SENDER_EMAIL') ? SENDER_EMAIL : 'noreply@tomsjavajive.com';
|
||||
$this->fromName = defined('SENDER_NAME') ? SENDER_NAME : "Tom's Java Jive";
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email via SendGrid API
|
||||
*/
|
||||
public function send(string $to, string $subject, string $htmlContent, ?string $textContent = null): array {
|
||||
$data = [
|
||||
'personalizations' => [
|
||||
[
|
||||
'to' => [['email' => $to]],
|
||||
'subject' => $subject
|
||||
]
|
||||
],
|
||||
'from' => [
|
||||
'email' => $this->fromEmail,
|
||||
'name' => $this->fromName
|
||||
],
|
||||
'content' => []
|
||||
];
|
||||
|
||||
if ($textContent) {
|
||||
$data['content'][] = ['type' => 'text/plain', 'value' => $textContent];
|
||||
|
||||
private function logEmail(string $to, string $subject, string $html, array $result, array $options = []): void {
|
||||
try {
|
||||
$preview = strip_tags($html);
|
||||
$preview = preg_replace('/\s+/', ' ', trim($preview));
|
||||
$preview = substr($preview, 0, 500);
|
||||
|
||||
// Find customer_id by email
|
||||
$customer = db()->fetch(
|
||||
"SELECT customer_id FROM customers WHERE email = :email LIMIT 1",
|
||||
['email' => $to]
|
||||
);
|
||||
|
||||
db()->insert('email_log', [
|
||||
'customer_id' => $customer['customer_id'] ?? null,
|
||||
'recipient_email'=> $to,
|
||||
'subject' => $subject,
|
||||
'preview' => $preview,
|
||||
'message_id' => $result['message_id'] ?? null,
|
||||
'status' => $result['success'] ? 'sent' : 'failed',
|
||||
'error_message' => $result['success'] ? null : ($result['error'] ?? null),
|
||||
'tags' => isset($options['tags']) ? implode(',', $options['tags']) : null,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// Never let logging break email sending
|
||||
}
|
||||
$data['content'][] = ['type' => 'text/html', 'value' => $htmlContent];
|
||||
|
||||
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
|
||||
}
|
||||
|
||||
public function checkDeliveryStatus(string $messageId): array {
|
||||
$ch = curl_init("https://platform.cyberpersons.com/email/v1/messages/" . urlencode($messageId));
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($data),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $this->apiKey,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->apiKey],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$body = json_decode($response, true);
|
||||
return $body['data'] ?? [];
|
||||
}
|
||||
|
||||
public function send(string $to, string $subject, string $html, ?string $text = null, array $options = []): array {
|
||||
$payload = array_merge([
|
||||
'from' => ['email' => $this->fromEmail, 'name' => getSetting('cybermail_from_name', "Tom's Java Jive")],
|
||||
'to' => [['email' => $to]],
|
||||
'subject' => $subject,
|
||||
'html' => $html,
|
||||
], $options);
|
||||
if ($text) $payload['text'] = $text;
|
||||
|
||||
$ch = curl_init('https://platform.cyberpersons.com/email/v1/send');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->apiKey, 'Content-Type: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
return ['success' => false, 'error' => $error];
|
||||
|
||||
$body = json_decode($response, true);
|
||||
if ($httpCode === 202) {
|
||||
$result = ['success' => true, 'message_id' => $body['data']['message_id'] ?? null];
|
||||
$this->logEmail($to, $subject, $html, $result, $options);
|
||||
return $result;
|
||||
}
|
||||
|
||||
// SendGrid returns 202 for accepted
|
||||
if ($httpCode >= 200 && $httpCode < 300) {
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
return ['success' => false, 'error' => $response, 'code' => $httpCode];
|
||||
$errorMsg = $body['error']['message'] ?? 'Unknown error';
|
||||
$result = ['success' => false, 'error' => $errorMsg, 'code' => $httpCode];
|
||||
$this->logEmail($to, $subject, $html, $result, $options);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send order confirmation email
|
||||
*/
|
||||
|
||||
public function sendOrderConfirmation(array $order): array {
|
||||
$items = json_decode($order['items'], true);
|
||||
$itemsHtml = '';
|
||||
foreach ($items as $item) {
|
||||
$itemsHtml .= sprintf(
|
||||
'<tr><td style="padding: 10px; border-bottom: 1px solid #eee;">%s</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee; text-align: center;">%d</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee; text-align: right;">%s</td></tr>',
|
||||
'<tr><td style="padding:10px;border-bottom:1px solid #eee;">%s</td>
|
||||
<td style="padding:10px;border-bottom:1px solid #eee;text-align:center;">%d</td>
|
||||
<td style="padding:10px;border-bottom:1px solid #eee;text-align:right;">%s</td></tr>',
|
||||
htmlspecialchars($item['name']),
|
||||
$item['quantity'],
|
||||
formatCurrency($item['total'])
|
||||
);
|
||||
}
|
||||
|
||||
$html = $this->getTemplate('order_confirmation', [
|
||||
'order_number' => $order['order_number'],
|
||||
'customer_name' => $order['customer_name'] ?? 'Valued Customer',
|
||||
'items_html' => $itemsHtml,
|
||||
'subtotal' => formatCurrency($order['subtotal']),
|
||||
'tax' => formatCurrency($order['tax']),
|
||||
'discount' => $order['discount'] > 0 ? '-' . formatCurrency($order['discount']) : '$0.00',
|
||||
'total' => formatCurrency($order['total']),
|
||||
'order_number' => $order['order_number'],
|
||||
'customer_name' => $order['customer_name'] ?? 'Valued Customer',
|
||||
'items_html' => $itemsHtml,
|
||||
'subtotal' => formatCurrency($order['subtotal']),
|
||||
'tax' => formatCurrency($order['tax']),
|
||||
'discount' => $order['discount'] > 0 ? '-' . formatCurrency($order['discount']) : '$0.00',
|
||||
'total' => formatCurrency($order['total']),
|
||||
'payment_method' => ucfirst($order['payment_method'] ?? 'N/A'),
|
||||
'order_date' => date('F j, Y', strtotime($order['created_at']))
|
||||
'order_date' => date('F j, Y', strtotime($order['created_at']))
|
||||
]);
|
||||
|
||||
return $this->send(
|
||||
$order['customer_email'],
|
||||
"Order Confirmation - #{$order['order_number']}",
|
||||
$html
|
||||
$html,
|
||||
null,
|
||||
['tags' => ['order', 'confirmation'], 'metadata' => ['order_id' => $order['order_number']]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send shipping notification email
|
||||
*/
|
||||
|
||||
public function sendShippingNotification(array $order): array {
|
||||
$html = $this->getTemplate('shipping_notification', [
|
||||
'order_number' => $order['order_number'],
|
||||
'customer_name' => $order['customer_name'] ?? 'Valued Customer',
|
||||
'order_number' => $order['order_number'],
|
||||
'customer_name' => $order['customer_name'] ?? 'Valued Customer',
|
||||
'tracking_number' => $order['tracking_number'],
|
||||
'tracking_url' => $order['tracking_url'] ?? '#',
|
||||
'carrier' => $order['shipping_carrier'] ?? 'Our shipping partner'
|
||||
'tracking_url' => $order['tracking_url'] ?? '#',
|
||||
'carrier' => $order['shipping_carrier'] ?? 'Our shipping partner'
|
||||
]);
|
||||
|
||||
return $this->send(
|
||||
$order['customer_email'],
|
||||
"Your Order Has Shipped - #{$order['order_number']}",
|
||||
$html
|
||||
$html,
|
||||
null,
|
||||
['tags' => ['order', 'shipping'], 'metadata' => ['order_id' => $order['order_number']]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send password reset email
|
||||
*/
|
||||
|
||||
public function sendPasswordReset(string $email, string $resetToken, string $name = ''): array {
|
||||
$resetUrl = SITE_URL . '/reset-password.php?token=' . $resetToken;
|
||||
|
||||
$html = $this->getTemplate('password_reset', [
|
||||
'customer_name' => $name ?: 'there',
|
||||
'reset_url' => $resetUrl,
|
||||
'expires' => '1 hour'
|
||||
'reset_url' => $resetUrl,
|
||||
'expires' => '1 hour'
|
||||
]);
|
||||
|
||||
return $this->send(
|
||||
$email,
|
||||
"Reset Your Password - Tom's Java Jive",
|
||||
$html
|
||||
);
|
||||
return $this->send($email, "Reset Your Password - Tom's Java Jive", $html, null, ['tags' => ['password-reset']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send welcome email to new customer
|
||||
*/
|
||||
|
||||
public function sendWelcome(string $email, string $name = ''): array {
|
||||
$html = $this->getTemplate('welcome', [
|
||||
'customer_name' => $name ?: 'Coffee Lover',
|
||||
'shop_url' => SITE_URL . '/shop.php'
|
||||
'shop_url' => SITE_URL . '/shop.php'
|
||||
]);
|
||||
|
||||
return $this->send(
|
||||
$email,
|
||||
"Welcome to Tom's Java Jive!",
|
||||
$html
|
||||
);
|
||||
return $this->send($email, "Welcome to Tom's Java Jive!", $html, null, ['tags' => ['welcome']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send abandoned cart reminder
|
||||
*/
|
||||
|
||||
public function sendAbandonedCartReminder(array $cart): array {
|
||||
$items = json_decode($cart['items'], true);
|
||||
$itemsHtml = '';
|
||||
foreach ($items as $item) {
|
||||
$itemsHtml .= sprintf(
|
||||
'<div style="padding: 10px; border-bottom: 1px solid #eee;">%s - %s</div>',
|
||||
'<div style="padding:10px;border-bottom:1px solid #eee;">%s - %s</div>',
|
||||
htmlspecialchars($item['name']),
|
||||
formatCurrency($item['price'])
|
||||
);
|
||||
}
|
||||
|
||||
$html = $this->getTemplate('abandoned_cart', [
|
||||
'items_html' => $itemsHtml,
|
||||
'total' => formatCurrency($cart['subtotal']),
|
||||
'cart_url' => SITE_URL . '/cart.php'
|
||||
'total' => formatCurrency($cart['subtotal']),
|
||||
'cart_url' => SITE_URL . '/cart.php'
|
||||
]);
|
||||
|
||||
return $this->send(
|
||||
$cart['customer_email'],
|
||||
"You left something behind!",
|
||||
$html
|
||||
);
|
||||
return $this->send($cart['customer_email'], "You left something behind!", $html, null, ['tags' => ['abandoned-cart']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email template with variables replaced
|
||||
*/
|
||||
|
||||
private function getTemplate(string $name, array $vars = []): string {
|
||||
$year = date('Y');
|
||||
$templates = [
|
||||
'order_confirmation' => '
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<div style="background: #FF5E1A; padding: 20px; text-align: center;">
|
||||
<h1 style="color: white; margin: 0;">Order Confirmed!</h1>
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#FF5E1A;padding:20px;text-align:center;">
|
||||
<h1 style="color:white;margin:0;">Order Confirmed!</h1>
|
||||
</div>
|
||||
<div style="padding: 30px; background: #fff;">
|
||||
<div style="padding:30px;background:#fff;">
|
||||
<p>Hi {{customer_name}},</p>
|
||||
<p>Thank you for your order! We\'ve received it and will begin processing right away.</p>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0;">Order #{{order_number}}</h3>
|
||||
<p style="color: #666; margin-bottom: 0;">{{order_date}}</p>
|
||||
<div style="background:#f9f9f9;padding:20px;border-radius:8px;margin:20px 0;">
|
||||
<h3 style="margin-top:0;">Order #{{order_number}}</h3>
|
||||
<p style="color:#666;margin-bottom:0;">{{order_date}}</p>
|
||||
</div>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<thead>
|
||||
<tr style="background: #f5f5f5;">
|
||||
<th style="padding: 10px; text-align: left;">Item</th>
|
||||
<th style="padding: 10px; text-align: center;">Qty</th>
|
||||
<th style="padding: 10px; text-align: right;">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{items_html}}
|
||||
</tbody>
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<thead><tr style="background:#f5f5f5;">
|
||||
<th style="padding:10px;text-align:left;">Item</th>
|
||||
<th style="padding:10px;text-align:center;">Qty</th>
|
||||
<th style="padding:10px;text-align:right;">Price</th>
|
||||
</tr></thead>
|
||||
<tbody>{{items_html}}</tbody>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 20px; text-align: right;">
|
||||
<p style="margin: 5px 0;">Subtotal: {{subtotal}}</p>
|
||||
<p style="margin: 5px 0;">Tax: {{tax}}</p>
|
||||
<p style="margin: 5px 0; color: #10B981;">Discount: {{discount}}</p>
|
||||
<p style="margin: 5px 0; font-size: 1.25em; font-weight: bold; color: #FF5E1A;">Total: {{total}}</p>
|
||||
<div style="margin-top:20px;text-align:right;">
|
||||
<p style="margin:5px 0;">Subtotal: {{subtotal}}</p>
|
||||
<p style="margin:5px 0;">Tax: {{tax}}</p>
|
||||
<p style="margin:5px 0;color:#10B981;">Discount: {{discount}}</p>
|
||||
<p style="margin:5px 0;font-size:1.25em;font-weight:bold;color:#FF5E1A;">Total: {{total}}</p>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 30px;">Payment Method: {{payment_method}}</p>
|
||||
|
||||
<p style="color: #666; font-size: 0.9em; margin-top: 30px;">
|
||||
If you have any questions, reply to this email or visit our website.
|
||||
</p>
|
||||
<p style="margin-top:30px;">Payment Method: {{payment_method}}</p>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p style="margin: 0;">© ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
<div style="background:#333;color:#fff;padding:20px;text-align:center;">
|
||||
<p style="margin:0;">© ' . $year . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
',
|
||||
|
||||
</div>',
|
||||
|
||||
'shipping_notification' => '
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<div style="background: #FF5E1A; padding: 20px; text-align: center;">
|
||||
<h1 style="color: white; margin: 0;">Your Order Has Shipped!</h1>
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#FF5E1A;padding:20px;text-align:center;">
|
||||
<h1 style="color:white;margin:0;">Your Order Has Shipped!</h1>
|
||||
</div>
|
||||
<div style="padding: 30px; background: #fff;">
|
||||
<div style="padding:30px;background:#fff;">
|
||||
<p>Hi {{customer_name}},</p>
|
||||
<p>Great news! Your order #{{order_number}} is on its way to you.</p>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 20px; border-radius: 8px; margin: 20px 0; text-align: center;">
|
||||
<p style="margin: 0 0 10px; color: #666;">Tracking Number</p>
|
||||
<p style="margin: 0; font-size: 1.25em; font-weight: bold;">{{tracking_number}}</p>
|
||||
<p style="margin: 10px 0 0;">Carrier: {{carrier}}</p>
|
||||
<p>Great news! Your order #{{order_number}} is on its way.</p>
|
||||
<div style="background:#f9f9f9;padding:20px;border-radius:8px;margin:20px 0;text-align:center;">
|
||||
<p style="margin:0 0 10px;color:#666;">Tracking Number</p>
|
||||
<p style="margin:0;font-size:1.25em;font-weight:bold;">{{tracking_number}}</p>
|
||||
<p style="margin:10px 0 0;">Carrier: {{carrier}}</p>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{tracking_url}}" style="display: inline-block; background: #FF5E1A; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: bold;">Track Your Package</a>
|
||||
<div style="text-align:center;margin:30px 0;">
|
||||
<a href="{{tracking_url}}" style="display:inline-block;background:#FF5E1A;color:white;padding:15px 30px;text-decoration:none;border-radius:8px;font-weight:bold;">Track Your Package</a>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; font-size: 0.9em;">
|
||||
Please allow 24-48 hours for tracking information to update.
|
||||
</p>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p style="margin: 0;">© ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
<div style="background:#333;color:#fff;padding:20px;text-align:center;">
|
||||
<p style="margin:0;">© ' . $year . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
',
|
||||
|
||||
</div>',
|
||||
|
||||
'password_reset' => '
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<div style="background: #FF5E1A; padding: 20px; text-align: center;">
|
||||
<h1 style="color: white; margin: 0;">Reset Your Password</h1>
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#FF5E1A;padding:20px;text-align:center;">
|
||||
<h1 style="color:white;margin:0;">Reset Your Password</h1>
|
||||
</div>
|
||||
<div style="padding: 30px; background: #fff;">
|
||||
<div style="padding:30px;background:#fff;">
|
||||
<p>Hi {{customer_name}},</p>
|
||||
<p>We received a request to reset your password. Click the button below to create a new password:</p>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{reset_url}}" style="display: inline-block; background: #FF5E1A; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: bold;">Reset Password</a>
|
||||
<p>We received a request to reset your password. Click the button below to create a new one:</p>
|
||||
<div style="text-align:center;margin:30px 0;">
|
||||
<a href="{{reset_url}}" style="display:inline-block;background:#FF5E1A;color:white;padding:15px 30px;text-decoration:none;border-radius:8px;font-weight:bold;">Reset Password</a>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; font-size: 0.9em;">
|
||||
This link will expire in {{expires}}. If you didn\'t request this, you can safely ignore this email.
|
||||
</p>
|
||||
<p style="color:#666;font-size:0.9em;">This link expires in {{expires}}. If you didn\'t request this, ignore this email.</p>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p style="margin: 0;">© ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
<div style="background:#333;color:#fff;padding:20px;text-align:center;">
|
||||
<p style="margin:0;">© ' . $year . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
',
|
||||
|
||||
</div>',
|
||||
|
||||
'welcome' => '
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<div style="background: #FF5E1A; padding: 30px; text-align: center;">
|
||||
<h1 style="color: white; margin: 0;">Welcome to the Family!</h1>
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#FF5E1A;padding:30px;text-align:center;">
|
||||
<h1 style="color:white;margin:0;">Welcome to the Family!</h1>
|
||||
</div>
|
||||
<div style="padding: 30px; background: #fff;">
|
||||
<div style="padding:30px;background:#fff;">
|
||||
<p>Hi {{customer_name}},</p>
|
||||
<p>Welcome to Tom\'s Java Jive! We\'re thrilled to have you join our community of coffee lovers.</p>
|
||||
|
||||
<h3>Here\'s what you can look forward to:</h3>
|
||||
<ul style="color: #666;">
|
||||
<ul style="color:#666;">
|
||||
<li>Exclusive member discounts</li>
|
||||
<li>Early access to new roasts</li>
|
||||
<li>Reward points on every purchase</li>
|
||||
<li>Birthday treats and special offers</li>
|
||||
</ul>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{shop_url}}" style="display: inline-block; background: #FF5E1A; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: bold;">Start Shopping</a>
|
||||
<div style="text-align:center;margin:30px 0;">
|
||||
<a href="{{shop_url}}" style="display:inline-block;background:#FF5E1A;color:white;padding:15px 30px;text-decoration:none;border-radius:8px;font-weight:bold;">Start Shopping</a>
|
||||
</div>
|
||||
|
||||
<p>Cheers,<br>The Tom\'s Java Jive Team</p>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p style="margin: 0;">© ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
<div style="background:#333;color:#fff;padding:20px;text-align:center;">
|
||||
<p style="margin:0;">© ' . $year . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
',
|
||||
|
||||
</div>',
|
||||
|
||||
'abandoned_cart' => '
|
||||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||||
<div style="background: #FF5E1A; padding: 20px; text-align: center;">
|
||||
<h1 style="color: white; margin: 0;">Forget Something?</h1>
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#FF5E1A;padding:20px;text-align:center;">
|
||||
<h1 style="color:white;margin:0;">Forget Something?</h1>
|
||||
</div>
|
||||
<div style="padding: 30px; background: #fff;">
|
||||
<div style="padding:30px;background:#fff;">
|
||||
<p>Hey there!</p>
|
||||
<p>We noticed you left some amazing items in your cart. Don\'t let them get away!</p>
|
||||
|
||||
<div style="background: #f9f9f9; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<div style="background:#f9f9f9;padding:20px;border-radius:8px;margin:20px 0;">
|
||||
{{items_html}}
|
||||
<div style="padding: 15px 10px; font-weight: bold; text-align: right;">
|
||||
Total: {{total}}
|
||||
</div>
|
||||
<div style="padding:15px 10px;font-weight:bold;text-align:right;">Total: {{total}}</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{cart_url}}" style="display: inline-block; background: #FF5E1A; color: white; padding: 15px 30px; text-decoration: none; border-radius: 8px; font-weight: bold;">Complete Your Order</a>
|
||||
<div style="text-align:center;margin:30px 0;">
|
||||
<a href="{{cart_url}}" style="display:inline-block;background:#FF5E1A;color:white;padding:15px 30px;text-decoration:none;border-radius:8px;font-weight:bold;">Complete Your Order</a>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; font-size: 0.9em;">
|
||||
Need help? Just reply to this email and we\'ll assist you!
|
||||
</p>
|
||||
</div>
|
||||
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
|
||||
<p style="margin: 0;">© ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
<div style="background:#333;color:#fff;padding:20px;text-align:center;">
|
||||
<p style="margin:0;">© ' . $year . ' Tom\'s Java Jive. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
'
|
||||
</div>'
|
||||
];
|
||||
|
||||
|
||||
$template = $templates[$name] ?? '<p>Email template not found.</p>';
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
$template = str_replace('{{' . $key . '}}', $value, $template);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for easy access
|
||||
function sendEmail(): SendGridEmail {
|
||||
function emailService(): Email {
|
||||
static $instance = null;
|
||||
if ($instance === null) {
|
||||
$instance = new SendGridEmail();
|
||||
$instance = new Email();
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
+15
-19
@@ -329,33 +329,29 @@ function getCartTotal() {
|
||||
* Send email via CyberMail API
|
||||
*/
|
||||
function sendEmail($to, $subject, $htmlContent, $textContent = '') {
|
||||
$apiKey = defined('CYBERMAIL_API_KEY') ? CYBERMAIL_API_KEY : '';
|
||||
if (!$apiKey) return false;
|
||||
|
||||
$payload = [
|
||||
'from' => ['email' => SENDER_EMAIL, 'name' => SENDER_NAME],
|
||||
'to' => [['email' => $to]],
|
||||
'subject' => $subject,
|
||||
'html' => $htmlContent,
|
||||
];
|
||||
$apiKey = getSetting('cybermail_api_key', defined('CYBERMAIL_API_KEY') ? CYBERMAIL_API_KEY : '');
|
||||
$from = getSetting('cybermail_from_email', 'noreply@tomsjavajive.com');
|
||||
$fromName = getSetting('cybermail_from_name', "Tom's Java Jive");
|
||||
if (!$apiKey) {
|
||||
error_log('[TJJ sendEmail] CYBERMAIL_API_KEY not configured');
|
||||
return false;
|
||||
}
|
||||
$payload = ['from' => ['email' => $from, 'name' => $fromName],
|
||||
'to' => [['email' => $to]], 'subject' => $subject, 'html' => $htmlContent];
|
||||
if ($textContent) $payload['text'] = $textContent;
|
||||
|
||||
$ch = curl_init('https://platform.cyberpersons.com/email/v1/send');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
|
||||
CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => false,
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return $httpCode === 202;
|
||||
if ($httpCode === 202) return true;
|
||||
error_log('[TJJ sendEmail] CyberMail HTTP ' . $httpCode . ' — ' . $response);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user