Files
2026-05-16 23:00:37 -05:00

370 lines
16 KiB
PHP

<?php
/**
* Tom's Java Jive - SendGrid Email Service
*
* Handles all transactional emails using SendGrid API
*/
class SendGridEmail {
private string $apiKey;
private string $fromEmail;
private string $fromName;
public function __construct() {
// Load from settings or config
$this->apiKey = getSetting('sendgrid_api_key', 'YOUR_SENDGRID_API_KEY_HERE');
$this->fromEmail = getSetting('sendgrid_from_email', 'noreply@tomsjavajive.com');
$this->fromName = getSetting('sendgrid_from_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];
}
$data['content'][] = ['type' => 'text/html', 'value' => $htmlContent];
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$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];
}
// SendGrid returns 202 for accepted
if ($httpCode >= 200 && $httpCode < 300) {
return ['success' => true];
}
return ['success' => false, 'error' => $response, 'code' => $httpCode];
}
/**
* 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>',
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']),
'payment_method' => ucfirst($order['payment_method'] ?? 'N/A'),
'order_date' => date('F j, Y', strtotime($order['created_at']))
]);
return $this->send(
$order['customer_email'],
"Order Confirmation - #{$order['order_number']}",
$html
);
}
/**
* 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',
'tracking_number' => $order['tracking_number'],
'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
);
}
/**
* 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'
]);
return $this->send(
$email,
"Reset Your Password - Tom's Java Jive",
$html
);
}
/**
* 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'
]);
return $this->send(
$email,
"Welcome to Tom's Java Jive!",
$html
);
}
/**
* 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>',
htmlspecialchars($item['name']),
formatCurrency($item['price'])
);
}
$html = $this->getTemplate('abandoned_cart', [
'items_html' => $itemsHtml,
'total' => formatCurrency($cart['subtotal']),
'cart_url' => SITE_URL . '/cart.php'
]);
return $this->send(
$cart['customer_email'],
"You left something behind!",
$html
);
}
/**
* Get email template with variables replaced
*/
private function getTemplate(string $name, array $vars = []): string {
$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>
<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>
<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>
<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>
</div>
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
<p style="margin: 0;">&copy; ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
</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>
<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>
</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>
<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;">&copy; ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
</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>
<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>
</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>
</div>
<div style="background: #333; color: #fff; padding: 20px; text-align: center;">
<p style="margin: 0;">&copy; ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
</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>
<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;">
<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>
<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;">&copy; ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
</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>
<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;">
{{items_html}}
<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>
<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;">&copy; ' . date('Y') . ' Tom\'s Java Jive. All rights reserved.</p>
</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 {
static $instance = null;
if ($instance === null) {
$instance = new SendGridEmail();
}
return $instance;
}