<?php
namespace App\Services;
use Exception;
/**
* Email Service
* Handles sending emails for various purposes
*/
class EmailService
{
/**
* @var mixed
*/
private $config;
/**
* @var mixed
*/
private $templates;
public function __construct()
{
$this->config = [
'driver' => $_ENV['MAIL_DRIVER'] ?? 'smtp',
'host' => $_ENV['MAIL_HOST'] ?? 'localhost',
'port' => (int) ($_ENV['MAIL_PORT'] ?? 587),
'username' => $_ENV['MAIL_USERNAME'] ?? '',
'password' => $_ENV['MAIL_PASSWORD'] ?? '',
'encryption' => $_ENV['MAIL_ENCRYPTION'] ?? 'tls',
'from_address' => $_ENV['MAIL_FROM_ADDRESS'] ?? 'noreply@example.com',
'from_name' => $_ENV['MAIL_FROM_NAME'] ?? 'E-commerce Store'
];
$this->templates = [
'verification' => $this->getVerificationTemplate(),
'password_reset' => $this->getPasswordResetTemplate(),
'welcome' => $this->getWelcomeTemplate(),
'order_confirmation' => $this->getOrderConfirmationTemplate()
];
}
/**
* Send verification email
*/
public function sendVerificationEmail(string $email, string $name, string $token): bool
{
$verificationUrl = $this->getAppUrl()."/api/auth/verify-email/{$token}";
$subject = 'Verify Your Email Address';
$body = $this->renderTemplate('verification', [
'name' => $name,
'verification_url' => $verificationUrl,
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($email, $subject, $body);
}
/**
* Send password reset email
*/
public function sendPasswordResetEmail(string $email, string $name, string $token): bool
{
$resetUrl = $this->getAppUrl()."/reset-password?token={$token}";
$subject = 'Reset Your Password';
$body = $this->renderTemplate('password_reset', [
'name' => $name,
'reset_url' => $resetUrl,
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($email, $subject, $body);
}
/**
* Send welcome email
*/
public function sendWelcomeEmail(string $email, string $name): bool
{
$subject = 'Welcome to '.$this->config['from_name'];
$body = $this->renderTemplate('welcome', [
'name' => $name,
'app_name' => $this->config['from_name'],
'app_url' => $this->getAppUrl()
]);
return $this->sendEmail($email, $subject, $body);
}
/**
* Send order confirmation email
*/
public function sendOrderConfirmationEmail(string $email, string $name, array $orderData): bool
{
$subject = 'Order Confirmation - '.$orderData['order_number'];
$body = $this->renderTemplate('order_confirmation', [
'name' => $name,
'order' => $orderData,
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($email, $subject, $body);
}
/**
* Send email using configured driver
*/
private function sendEmail(string $to, string $subject, string $body): bool
{
try {
switch ($this->config['driver']) {
case 'smtp':
return $this->sendViaSMTP($to, $subject, $body);
case 'mail':
return $this->sendViaMail($to, $subject, $body);
case 'log':
return $this->sendViaLog($to, $subject, $body);
default:
throw new Exception('Unsupported mail driver: '.$this->config['driver']);
}
} catch (Exception $e) {
error_log('Email sending failed: '.$e->getMessage());
return false;
}
}
/**
* Send email via SMTP
*/
private function sendViaSMTP(string $to, string $subject, string $body): bool
{
// For production, you would implement proper SMTP sending
// For now, we'll log the email
return $this->sendViaLog($to, $subject, $body);
}
/**
* Send email via PHP mail() function
*/
private function sendViaMail(string $to, string $subject, string $body): bool
{
$headers = [
'From: '.$this->config['from_name'].' <'.$this->config['from_address'].'>',
'Reply-To: '.$this->config['from_address'],
'Content-Type: text/html; charset=UTF-8',
'MIME-Version: 1.0'
];
return mail($to, $subject, $body, implode("\r\n", $headers));
}
/**
* Log email instead of sending (for development)
*/
private function sendViaLog(string $to, string $subject, string $body): bool
{
$logEntry = [
'timestamp' => date('Y-m-d H:i:s'),
'to' => $to,
'subject' => $subject,
'body' => $body
];
$logFile = __DIR__.'/../../storage/logs/emails.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
@mkdir($logDir, 0755, true);
}
return file_put_contents($logFile, json_encode($logEntry)."\n", FILE_APPEND | LOCK_EX) !== false;
}
/**
* Render email template
*/
private function renderTemplate(string $template, array $variables): string
{
if (!isset($this->templates[$template])) {
throw new Exception("Email template '{$template}' not found");
}
$content = $this->templates[$template];
// Replace variables
foreach ($variables as $key => $value) {
if (is_array($value)) {
// Handle complex variables (like order data)
$content = $this->replaceComplexVariable($content, $key, $value);
} else {
$content = str_replace('{{'.$key.'}}', $value, $content);
}
}
return $content;
}
/**
* Replace complex variables in templates
*/
private function replaceComplexVariable(string $content, string $key, array $data): string
{
// Handle order data
if ($key === 'order') {
$content = str_replace('{{order.number}}', $data['order_number'] ?? '', $content);
$content = str_replace('{{order.total}}', number_format($data['total_amount'] ?? 0, 2), $content);
$content = str_replace('{{order.status}}', $data['status'] ?? '', $content);
$content = str_replace('{{order.date}}', date('F j, Y', strtotime($data['created_at'] ?? 'now')), $content);
// Handle order items
if (isset($data['items'])) {
$itemsHtml = '';
foreach ($data['items'] as $item) {
$itemsHtml .= '<tr>';
$itemsHtml .= '<td>'.htmlspecialchars($item['product_name'] ?? '').'</td>';
$itemsHtml .= '<td>'.($item['quantity'] ?? 0).'</td>';
$itemsHtml .= '<td>฿'.number_format($item['price'] ?? 0, 2).'</td>';
$itemsHtml .= '<td>฿'.number_format($item['total'] ?? 0, 2).'</td>';
$itemsHtml .= '</tr>';
}
$content = str_replace('{{order.items}}', $itemsHtml, $content);
}
}
return $content;
}
/**
* Get application URL
*/
private function getAppUrl(): string
{
return $_ENV['APP_URL'] ?? 'http://localhost';
}
/**
* Get verification email template
*/
private function getVerificationTemplate(): string
{
return '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Verify Your Email</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{app_name}}</h1>
</div>
<div class="content">
<h2>Verify Your Email Address</h2>
<p>Hello {{name}},</p>
<p>Thank you for registering with {{app_name}}. Please click the button below to verify your email address:</p>
<p style="text-align: center;">
<a href="{{verification_url}}" class="button">Verify Email</a>
</p>
<p>If you cannot click the button, copy and paste this link into your browser:</p>
<p><a href="{{verification_url}}">{{verification_url}}</a></p>
<p>If you did not create an account, please ignore this email.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
}
/**
* Get password reset email template
*/
private function getPasswordResetTemplate(): string
{
return '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Reset Your Password</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #dc3545; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.button { display: inline-block; padding: 12px 24px; background: #dc3545; color: white; text-decoration: none; border-radius: 4px; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{app_name}}</h1>
</div>
<div class="content">
<h2>Reset Your Password</h2>
<p>Hello {{name}},</p>
<p>You have requested to reset your password. Click the button below to reset it:</p>
<p style="text-align: center;">
<a href="{{reset_url}}" class="button">Reset Password</a>
</p>
<p>If you cannot click the button, copy and paste this link into your browser:</p>
<p><a href="{{reset_url}}">{{reset_url}}</a></p>
<p>This link will expire in 1 hour for security reasons.</p>
<p>If you did not request a password reset, please ignore this email.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
}
/**
* Get welcome email template
*/
private function getWelcomeTemplate(): string
{
return '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #28a745; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.button { display: inline-block; padding: 12px 24px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Welcome to {{app_name}}!</h1>
</div>
<div class="content">
<h2>Welcome {{name}}!</h2>
<p>Thank you for joining {{app_name}}. We are excited to have you as part of our community.</p>
<p>You can now:</p>
<ul>
<li>Browse our products</li>
<li>Add items to your wishlist</li>
<li>Track your orders</li>
<li>Manage your profile and addresses</li>
</ul>
<p style="text-align: center;">
<a href="{{app_url}}" class="button">Start Shopping</a>
</p>
<p>If you have any questions, feel free to contact our support team.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
}
/**
* Get order confirmation email template
*/
private function getOrderConfirmationTemplate(): string
{
return '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Order Confirmation</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #007bff; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.order-details { background: white; padding: 15px; margin: 15px 0; border-radius: 4px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #f8f9fa; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Order Confirmation</h1>
</div>
<div class="content">
<h2>Thank you for your order, {{name}}!</h2>
<p>Your order has been received and is being processed.</p>
<div class="order-details">
<h3>Order Details</h3>
<p><strong>Order Number:</strong> {{order.number}}</p>
<p><strong>Order Date:</strong> {{order.date}}</p>
<p><strong>Status:</strong> {{order.status}}</p>
<h4>Items Ordered</h4>
<table>
<thead>
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{{order.items}}
</tbody>
</table>
<p style="text-align: right; font-size: 18px; font-weight: bold;">
Total: ฿{{order.total}}
</p>
</div>
<p>We will send you another email when your order ships.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
}
/**
* Send order status update email
*/
public function sendOrderStatusUpdate(string $email, string $name, array $orderData, string $newStatus): bool
{
$statusMessages = [
'confirmed' => 'Your order has been confirmed and is being prepared',
'processing' => 'Your order is being processed and will be shipped soon',
'shipped' => 'Your order has been shipped and is on its way',
'delivered' => 'Your order has been delivered successfully',
'cancelled' => 'Your order has been cancelled'
];
$subject = 'Order Status Update - '.$orderData['order_number'];
$message = $this->getOrderStatusTemplate([
'customer_name' => $name,
'order_number' => $orderData['order_number'],
'new_status' => ucfirst($newStatus),
'status_message' => $statusMessages[$newStatus] ?? 'Your order status has been updated',
'order_total' => number_format($orderData['total_amount'], 2),
'tracking_url' => $this->getAppUrl()."/orders/{$orderData['order_number']}/track",
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($email, $subject, $message);
}
/**
* Send order cancellation email
*/
public function sendOrderCancellation(array $orderData): bool
{
$shippingAddress = is_string($orderData['shipping_address']) ?
json_decode($orderData['shipping_address'], true) :
$orderData['shipping_address'];
$customerName = $shippingAddress['first_name'].' '.$shippingAddress['last_name'];
$subject = 'Order Cancelled - '.$orderData['order_number'];
$message = $this->getCancellationTemplate([
'customer_name' => $customerName,
'order_number' => $orderData['order_number'],
'order_total' => number_format($orderData['total_amount'], 2),
'cancellation_reason' => $orderData['notes'] ?? 'Order cancelled by customer',
'refund_info' => 'If payment was made, refund will be processed within 3-5 business days',
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($orderData['email'], $subject, $message);
}
/**
* Send low stock alert to admin
*/
public function sendLowStockAlert(array $productData): bool
{
$adminEmail = $_ENV['ADMIN_EMAIL'] ?? 'admin@example.com';
$subject = 'Low Stock Alert - '.$productData['name'];
$message = $this->getLowStockTemplate([
'product_name' => $productData['name'],
'product_sku' => $productData['sku'],
'current_stock' => $productData['current_stock'],
'min_stock_level' => $productData['min_stock_level'],
'product_url' => $this->getAppUrl()."/products/{$productData['id']}",
'app_name' => $this->config['from_name']
]);
return $this->sendEmail($adminEmail, $subject, $message);
}
/**
* Get order status update template
*/
private function getOrderStatusTemplate(array $data): string
{
$template = '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Order Status Update</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #28a745; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.status-badge { display: inline-block; padding: 8px 16px; background: #28a745; color: white; border-radius: 20px; font-weight: bold; }
.order-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
.button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{app_name}}</h1>
</div>
<div class="content">
<h2>Order Status Update</h2>
<p>Dear {{customer_name}},</p>
<p>{{status_message}}</p>
<div class="order-details">
<p><strong>Order Number:</strong> {{order_number}}</p>
<p><strong>Status:</strong> <span class="status-badge">{{new_status}}</span></p>
<p><strong>Order Total:</strong> ฿{{order_total}}</p>
</div>
<p style="text-align: center;">
<a href="{{tracking_url}}" class="button">Track Your Order</a>
</p>
<p>Thank you for shopping with us!</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
foreach ($data as $key => $value) {
$template = str_replace('{{'.$key.'}}', $value, $template);
}
return $template;
}
/**
* Get cancellation email template
*/
private function getCancellationTemplate(array $data): string
{
$template = '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Order Cancelled</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #dc3545; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.order-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{app_name}}</h1>
</div>
<div class="content">
<h2>Order Cancellation</h2>
<p>Dear {{customer_name}},</p>
<p>Your order has been cancelled as requested.</p>
<div class="order-details">
<p><strong>Order Number:</strong> {{order_number}}</p>
<p><strong>Order Total:</strong> ฿{{order_total}}</p>
<p><strong>Reason:</strong> {{cancellation_reason}}</p>
</div>
<p>{{refund_info}}</p>
<p>We apologize for any inconvenience and hope to serve you again in the future.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
foreach ($data as $key => $value) {
$template = str_replace('{{'.$key.'}}', $value, $template);
}
return $template;
}
/**
* Get low stock alert template
*/
private function getLowStockTemplate(array $data): string
{
$template = '
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Low Stock Alert</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #ffc107; color: #212529; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
.product-details { background: white; padding: 15px; border-radius: 5px; margin: 15px 0; }
.button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }
.footer { padding: 20px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{app_name}} - Admin Alert</h1>
</div>
<div class="content">
<h2>⚠️ Low Stock Alert</h2>
<div class="alert">
<p><strong>Action Required:</strong> The following product is running low on stock and needs to be restocked.</p>
</div>
<div class="product-details">
<p><strong>Product:</strong> {{product_name}}</p>
<p><strong>SKU:</strong> {{product_sku}}</p>
<p><strong>Current Stock:</strong> {{current_stock}} units</p>
<p><strong>Minimum Level:</strong> {{min_stock_level}} units</p>
</div>
<p style="text-align: center;">
<a href="{{product_url}}" class="button">Manage Product</a>
</p>
<p>Please restock this item as soon as possible to avoid stockouts.</p>
</div>
<div class="footer">
<p>© 2024 {{app_name}}. All rights reserved.</p>
</div>
</div>
</body>
</html>';
foreach ($data as $key => $value) {
$template = str_replace('{{'.$key.'}}', $value, $template);
}
return $template;
}
}