PHP项目邮件营销系统设计

wen PHP项目 2

本文目录导读:

PHP项目邮件营销系统设计

  1. 系统架构设计
  2. 部署建议
  3. 安全注意事项

我将为您设计一个完整的PHP邮件营销系统架构,这个系统将包含订阅管理、模板制作、发送队列、统计分析等核心功能。

系统架构设计

数据库设计

-- 订阅者表
CREATE TABLE subscribers (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(100),
    status ENUM('active', 'unsubscribed', 'bounced', 'spam') DEFAULT 'active',
    source VARCHAR(50),
    ip_address VARCHAR(45),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    unsubscribed_at TIMESTAMP NULL,
    INDEX idx_email (email),
    INDEX idx_status (status),
    INDEX idx_created (created_at)
);
-- 订阅分组表
CREATE TABLE subscriber_groups (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 订阅者-分组关联表
CREATE TABLE subscriber_group_members (
    subscriber_id INT,
    group_id INT,
    subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (subscriber_id, group_id),
    FOREIGN KEY (subscriber_id) REFERENCES subscribers(id) ON DELETE CASCADE,
    FOREIGN KEY (group_id) REFERENCES subscriber_groups(id) ON DELETE CASCADE
);
-- 邮件模板表
CREATE TABLE email_templates (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    subject VARCHAR(500) NOT NULL,
    content TEXT NOT NULL,
    content_type ENUM('text', 'html') DEFAULT 'html',
    variables JSON,
    created_by INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 发送任务表
CREATE TABLE campaigns (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    template_id INT,
    group_ids JSON,
    subject VARCHAR(500),
    sender_name VARCHAR(100),
    sender_email VARCHAR(255),
    reply_to VARCHAR(255),
    status ENUM('draft', 'scheduled', 'sending', 'completed', 'paused', 'cancelled') DEFAULT 'draft',
    scheduled_at TIMESTAMP NULL,
    started_at TIMESTAMP NULL,
    completed_at TIMESTAMP NULL,
    total_recipients INT DEFAULT 0,
    sent_count INT DEFAULT 0,
    opened_count INT DEFAULT 0,
    clicked_count INT DEFAULT 0,
    bounced_count INT DEFAULT 0,
    unsubscribed_count INT DEFAULT 0,
    created_by INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (template_id) REFERENCES email_templates(id)
);
-- 邮件发送队列表
CREATE TABLE email_queue (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    campaign_id INT,
    subscriber_id INT,
    email VARCHAR(255) NOT NULL,
    subject VARCHAR(500),
    content TEXT,
    status ENUM('pending', 'sending', 'sent', 'failed', 'opened', 'clicked', 'bounced', 'unsubscribed') DEFAULT 'pending',
    attempts INT DEFAULT 0,
    max_attempts INT DEFAULT 3,
    error_message TEXT,
    sent_at TIMESTAMP NULL,
    opened_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_campaign_status (campaign_id, status),
    INDEX idx_subscriber (subscriber_id),
    INDEX idx_created (created_at),
    FOREIGN KEY (campaign_id) REFERENCES campaigns(id),
    FOREIGN KEY (subscriber_id) REFERENCES subscribers(id)
);
-- 邮件点击追踪表
CREATE TABLE email_tracking (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    queue_id BIGINT,
    subscriber_id INT,
    click_url VARCHAR(2000),
    ip_address VARCHAR(45),
    user_agent TEXT,
    clicked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (queue_id) REFERENCES email_queue(id),
    FOREIGN KEY (subscriber_id) REFERENCES subscribers(id)
);

核心类设计

<?php
// MailMarketingSystem.php
class MailMarketingSystem {
    private $db;
    private $config;
    private $mailer;
    public function __construct(PDO $db, array $config) {
        $this->db = $db;
        $this->config = $config;
        $this->mailer = $this->initMailer();
    }
    // 订阅管理
    public function subscribe(string $email, string $name = '', array $groups = [], string $source = 'webform'): bool {
        try {
            // 验证邮箱
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                throw new Exception('Invalid email address');
            }
            // 检查是否已存在
            $existing = $this->getSubscriberByEmail($email);
            if ($existing) {
                // 更新订阅状态(如果之前退订了)
                if ($existing['status'] === 'unsubscribed') {
                    $this->reactivateSubscriber($existing['id']);
                }
                return true;
            }
            // 开启事务
            $this->db->beginTransaction();
            // 插入订阅者
            $stmt = $this->db->prepare("
                INSERT INTO subscribers (email, name, status, source, ip_address) 
                VALUES (:email, :name, 'active', :source, :ip)
            ");
            $stmt->execute([
                'email' => $email,
                'name' => $name,
                'source' => $source,
                'ip' => $_SERVER['REMOTE_ADDR'] ?? ''
            ]);
            $subscriberId = $this->db->lastInsertId();
            // 添加到分组
            if (!empty($groups)) {
                $this->addSubscriberToGroups($subscriberId, $groups);
            }
            $this->db->commit();
            // 发送确认邮件(可选)
            $this->sendConfirmationEmail($email, $name);
            return true;
        } catch (Exception $e) {
            $this->db->rollBack();
            $this->logError('Subscribe failed: ' . $e->getMessage());
            return false;
        }
    }
    // 取消订阅
    public function unsubscribe(string $email): bool {
        try {
            $stmt = $this->db->prepare("
                UPDATE subscribers 
                SET status = 'unsubscribed', unsubscribed_at = NOW() 
                WHERE email = :email
            ");
            return $stmt->execute(['email' => $email]);
        } catch (Exception $e) {
            $this->logError('Unsubscribe failed: ' . $e->getMessage());
            return false;
        }
    }
    // 创建邮件模板
    public function createTemplate(string $name, string $subject, string $content, array $variables = []): int {
        // 预处理模板变量
        $processedContent = $this->processTemplateVariables($content, $variables);
        $stmt = $this->db->prepare("
            INSERT INTO email_templates (name, subject, content, variables) 
            VALUES (:name, :subject, :content, :variables)
        ");
        $stmt->execute([
            'name' => $name,
            'subject' => $subject,
            'content' => $processedContent,
            'variables' => json_encode($variables)
        ]);
        return $this->db->lastInsertId();
    }
    // 创建发送任务
    public function createCampaign(array $data): int {
        $stmt = $this->db->prepare("
            INSERT INTO campaigns (
                name, template_id, group_ids, subject, 
                sender_name, sender_email, reply_to, 
                scheduled_at, status, created_by
            ) VALUES (
                :name, :template_id, :group_ids, :subject,
                :sender_name, :sender_email, :reply_to,
                :scheduled_at, 'draft', :created_by
            )
        ");
        $stmt->execute([
            'name' => $data['name'],
            'template_id' => $data['template_id'],
            'group_ids' => json_encode($data['group_ids']),
            'subject' => $data['subject'] ?? '',
            'sender_name' => $data['sender_name'] ?? $this->config['default_sender_name'],
            'sender_email' => $data['sender_email'] ?? $this->config['default_sender_email'],
            'reply_to' => $data['reply_to'] ?? '',
            'scheduled_at' => $data['scheduled_at'] ?? null,
            'created_by' => $data['created_by']
        ]);
        return $this->db->lastInsertId();
    }
    // 准备邮件队列
    public function prepareCampaignQueue(int $campaignId): int {
        $campaign = $this->getCampaign($campaignId);
        if (!$campaign || $campaign['status'] !== 'draft') {
            return 0;
        }
        // 获取目标订阅者
        $subscribers = $this->getCampaignSubscribers($campaign);
        // 获取模板内容
        $template = $this->getTemplate($campaign['template_id']);
        $count = 0;
        $this->db->beginTransaction();
        try {
            $stmt = $this->db->prepare("
                INSERT INTO email_queue (campaign_id, subscriber_id, email, subject, content, status)
                VALUES (:campaign_id, :subscriber_id, :email, :subject, :content, 'pending')
            ");
            foreach ($subscribers as $subscriber) {
                // 个性化处理
                $personalized = $this->personalizeEmail($template, $subscriber);
                $stmt->execute([
                    'campaign_id' => $campaignId,
                    'subscriber_id' => $subscriber['id'],
                    'email' => $subscriber['email'],
                    'subject' => $personalized['subject'],
                    'content' => $personalized['content']
                ]);
                $count++;
            }
            // 更新任务统计
            $this->db->prepare("
                UPDATE campaigns 
                SET total_recipients = :count, status = 'scheduled' 
                WHERE id = :id
            ")->execute(['count' => $count, 'id' => $campaignId]);
            $this->db->commit();
        } catch (Exception $e) {
            $this->db->rollBack();
            $this->logError('Prepare queue failed: ' . $e->getMessage());
            return 0;
        }
        return $count;
    }
    // 发送邮件(批量处理)
    public function sendCampaignEmails(int $campaignId, int $limit = 50): array {
        $result = ['sent' => 0, 'failed' => 0];
        // 获取待发送的邮件
        $stmt = $this->db->prepare("
            SELECT * FROM email_queue 
            WHERE campaign_id = :campaign_id AND status = 'pending'
            LIMIT :limit
        ");
        $stmt->execute(['campaign_id' => $campaignId, 'limit' => $limit]);
        $emails = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach ($emails as $email) {
            try {
                // 更新状态为发送中
                $this->updateQueueStatus($email['id'], 'sending');
                // 添加追踪像素
                $trackingPixel = $this->generateTrackingPixel($email['id']);
                $email['content'] = str_replace('</body>', $trackingPixel . '</body>', $email['content']);
                // 替换链接为追踪链接
                $email['content'] = $this->replaceLinksWithTracking($email['content'], $email['id']);
                // 发送邮件
                $sent = $this->mailer->send([
                    'to' => $email['email'],
                    'subject' => $email['subject'],
                    'body' => $email['content'],
                    'tracking_id' => $email['id']
                ]);
                if ($sent) {
                    $this->updateQueueStatus($email['id'], 'sent', ['sent_at' => date('Y-m-d H:i:s')]);
                    $this->incrementCampaignCounter($campaignId, 'sent_count');
                    $result['sent']++;
                } else {
                    throw new Exception('Failed to send email');
                }
            } catch (Exception $e) {
                $this->handleSendFailure($email, $e->getMessage());
                $result['failed']++;
            }
            // 速率限制
            usleep(200000); // 200ms 延迟
        }
        // 检查是否完成
        $this->checkCampaignCompletion($campaignId);
        return $result;
    }
    // 处理退信
    public function handleBounce(string $email, string $bounceType = 'hard'): void {
        $status = $bounceType === 'hard' ? 'bounced' : 'soft_bounce';
        $stmt = $this->db->prepare("
            UPDATE subscribers SET status = :status WHERE email = :email
        ");
        $stmt->execute(['status' => $status, 'email' => $email]);
        // 更新发送队列
        $this->db->prepare("
            UPDATE email_queue 
            SET status = 'bounced', error_message = :type 
            WHERE email = :email AND status IN ('sent', 'pending')
        ")->execute(['type' => $bounceType, 'email' => $email]);
    }
    // 处理打开追踪
    public function trackOpen(string $trackingId): void {
        $stmt = $this->db->prepare("
            UPDATE email_queue 
            SET status = 'opened', opened_at = NOW() 
            WHERE id = :id AND status = 'sent'
        ");
        $stmt->execute(['id' => base64_decode($trackingId)]);
    }
    // 处理点击追踪
    public function trackClick(string $trackingId, string $url): void {
        $queueId = base64_decode($trackingId);
        $stmt = $this->db->prepare("
            INSERT INTO email_tracking (queue_id, subscriber_id, click_url, ip_address, user_agent)
            SELECT :queue_id, subscriber_id, :url, :ip, :ua
            FROM email_queue WHERE id = :queue_id2
        ");
        $stmt->execute([
            'queue_id' => $queueId,
            'url' => $url,
            'ip' => $_SERVER['REMOTE_ADDR'],
            'ua' => $_SERVER['HTTP_USER_AGENT'],
            'queue_id2' => $queueId
        ]);
        // 更新状态
        $this->db->prepare("
            UPDATE email_queue SET status = 'clicked' WHERE id = :id AND status = 'opened'
        ")->execute(['id' => $queueId]);
    }
    // 生成统计报告
    public function getCampaignStats(int $campaignId): array {
        $campaign = $this->getCampaign($campaignId);
        $stats = [
            'total' => $campaign['total_recipients'],
            'sent' => $campaign['sent_count'],
            'opened' => $campaign['opened_count'],
            'clicked' => $campaign['clicked_count'],
            'bounced' => $campaign['bounced_count'],
            'unsubscribed' => $campaign['unsubscribed_count'],
        ];
        // 计算比率
        $stats['open_rate'] = $stats['sent'] > 0 ? round($stats['opened'] / $stats['sent'] * 100, 2) : 0;
        $stats['click_rate'] = $stats['opened'] > 0 ? round($stats['clicked'] / $stats['opened'] * 100, 2) : 0;
        $stats['bounce_rate'] = $stats['total'] > 0 ? round($stats['bounced'] / $stats['total'] * 100, 2) : 0;
        return $stats;
    }
    // 私有辅助方法
    private function initMailer() {
        // 初始化邮件发送器(支持SMTP、Sendmail等)
        return new Mailer($this->config['mail']);
    }
    private function personalizeEmail(array $template, array $subscriber): array {
        $variables = json_decode($template['variables'], true) ?? [];
        $replacements = [
            '{{name}}' => $subscriber['name'],
            '{{email}}' => $subscriber['email'],
            '{{unsubscribe_url}}' => $this->generateUnsubscribeUrl($subscriber['email']),
        ];
        // 自定义变量替换
        foreach ($variables as $var => $field) {
            $replacements["{{{$var}}}"] = $subscriber[$field] ?? '';
        }
        $subject = strtr($template['subject'], $replacements);
        $content = strtr($template['content'], $replacements);
        return ['subject' => $subject, 'content' => $content];
    }
    private function generateTrackingPixel(int $queueId): string {
        $encoded = base64_encode($queueId);
        $url = $this->config['tracking_url'] . "/open/{$encoded}.gif";
        return "<img src=\"{$url}\" width=\"1\" height=\"1\" alt=\"\" />";
    }
    private function replaceLinksWithTracking(string $content, int $queueId): string {
        $encoded = base64_encode($queueId);
        $trackingUrl = $this->config['tracking_url'] . "/click/{$encoded}?url=";
        return preg_replace_callback(
            '/href=["\'](https?:\/\/[^"\']+)["\']/i',
            function($matches) use ($trackingUrl) {
                return 'href="' . $trackingUrl . urlencode($matches[1]) . '"';
            },
            $content
        );
    }
    private function handleSendFailure(array $email, string $error): void {
        $attempts = $email['attempts'] + 1;
        if ($attempts >= $email['max_attempts']) {
            $status = 'failed';
        } else {
            $status = 'pending';
        }
        $this->db->prepare("
            UPDATE email_queue 
            SET status = :status, attempts = :attempts, error_message = :error 
            WHERE id = :id
        ")->execute([
            'status' => $status,
            'attempts' => $attempts,
            'error' => $error,
            'id' => $email['id']
        ]);
    }
    private function checkCampaignCompletion(int $campaignId): void {
        $stmt = $this->db->prepare("
            SELECT COUNT(*) as pending 
            FROM email_queue 
            WHERE campaign_id = :id AND status IN ('pending', 'sending')
        ");
        $stmt->execute(['id' => $campaignId]);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($result['pending'] == 0) {
            $this->db->prepare("
                UPDATE campaigns 
                SET status = 'completed', completed_at = NOW() 
                WHERE id = :id
            ")->execute(['id' => $campaignId]);
        }
    }
    // ... 其他辅助方法
}

命令行发送处理器

<?php
// cron/send_emails.php
require_once __DIR__ . '/../bootstrap.php';
$system = new MailMarketingSystem($db, $config);
// 获取所有待发送的任务
$stmt = $db->prepare("
    SELECT id FROM campaigns 
    WHERE status IN ('scheduled', 'sending') 
    AND (scheduled_at IS NULL OR scheduled_at <= NOW())
    ORDER BY created_at ASC
");
$stmt->execute();
$campaigns = $stmt->fetchAll(PDO::FETCH_COLUMN);
foreach ($campaigns as $campaignId) {
    // 更新状态为发送中
    $db->prepare("UPDATE campaigns SET status = 'sending', started_at = NOW() WHERE id = :id")
       ->execute(['id' => $campaignId]);
    // 分批发送
    $batchSize = 50;
    $totalSent = 0;
    while (true) {
        $result = $system->sendCampaignEmails($campaignId, $batchSize);
        $totalSent += $result['sent'];
        if ($result['sent'] == 0) {
            break;
        }
        // 检查是否应该暂停(根据内存使用或时间限制)
        if (memory_get_usage(true) > 100 * 1024 * 1024) { // 100MB
            break;
        }
    }
    echo "Campaign #{$campaignId}: Sent {$totalSent} emails\n";
}

订阅管理表单示例

<!-- subscribe_form.php -->
<!DOCTYPE html>
<html>
<head>订阅我们的邮件</title>
    <style>
        .subscribe-form { max-width: 400px; margin: 50px auto; padding: 20px; }
        .form-group { margin-bottom: 15px; }
        .form-group label { display: block; margin-bottom: 5px; }
        .form-group input[type="email"],
        .form-group input[type="text"] { width: 100%; padding: 8px; box-sizing: border-box; }
        .submit-btn { background: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; }
        .message { padding: 10px; margin-bottom: 15px; border-radius: 4px; }
        .success { background: #d4edda; color: #155724; }
        .error { background: #f8d7da; color: #721c24; }
    </style>
</head>
<body>
    <div class="subscribe-form">
        <?php if (isset($_GET['status'])): ?>
            <?php if ($_GET['status'] === 'success'): ?>
                <div class="message success">订阅成功!请查收确认邮件。</div>
            <?php elseif ($_GET['status'] === 'exists'): ?>
                <div class="message success">您已经订阅过了。</div>
            <?php else: ?>
                <div class="message error">订阅失败,请稍后重试。</div>
            <?php endif; ?>
        <?php endif; ?>
        <h2>订阅我们的邮件通讯</h2>
        <form method="POST" action="process_subscribe.php">
            <div class="form-group">
                <label for="name">姓名</label>
                <input type="text" id="name" name="name" required>
            </div>
            <div class="form-group">
                <label for="email">邮箱地址</label>
                <input type="email" id="email" name="email" required>
            </div>
            <div class="form-group">
                <label>感兴趣的主题</label><br>
                <label><input type="checkbox" name="interests[]" value="news"> 新闻资讯</label><br>
                <label><input type="checkbox" name="interests[]" value="promotions"> 优惠活动</label><br>
                <label><input type="checkbox" name="interests[]" value="updates"> 产品更新</label>
            </div>
            <button type="submit" class="submit-btn">立即订阅</button>
        </form>
        <p style="margin-top: 20px; font-size: 12px; color: #666;">
            我们尊重您的隐私,绝不会分享您的信息,您可以随时取消订阅。
        </p>
    </div>
</body>
</html>

退订处理页面

<?php
// unsubscribe.php
require_once 'config/database.php';
require_once 'classes/MailMarketingSystem.php';
$system = new MailMarketingSystem($db, $config);
$email = $_GET['email'] ?? '';
$token = $_GET['token'] ?? '';
// 验证退订令牌
if (empty($email) || empty($token) || !$system->verifyUnsubscribeToken($email, $token)) {
    die('Invalid unsubscribe link');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if ($_POST['confirm'] === 'yes') {
        $system->unsubscribe($email);
        $message = '您已成功取消订阅。';
        $success = true;
    } else {
        header('Location: /');
        exit;
    }
}
?>
<!DOCTYPE html>
<html>
<head>取消订阅</title>
</head>
<body>
    <div style="max-width: 500px; margin: 50px auto; text-align: center; padding: 20px;">
        <?php if (isset($message)): ?>
            <h2><?= htmlspecialchars($message) ?></h2>
            <p>如果您改变主意,随时可以重新订阅。</p>
        <?php else: ?>
            <h2>确定要取消订阅吗?</h2>
            <p>您将停止接收来自我们所有的邮件通讯。</p>
            <form method="POST">
                <button type="submit" name="confirm" value="yes" style="background: #dc3545; color: white; padding: 10px 20px; border: none; cursor: pointer;">
                    确认取消订阅
                </button>
                <button type="submit" name="confirm" value="no" style="background: #6c757d; color: white; padding: 10px 20px; border: none; cursor: pointer; margin-left: 10px;">
                    保持订阅
                </button>
            </form>
        <?php endif; ?>
    </div>
</body>
</html>

API端点示例

<?php
// api/email_marketing.php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
require_once '../config/database.php';
require_once '../classes/MailMarketingSystem.php';
$system = new MailMarketingSystem($db, $config);
$method = $_SERVER['REQUEST_METHOD'];
$endpoint = $_GET['endpoint'] ?? '';
// API认证
function authenticate() {
    $headers = getallheaders();
    $token = $headers['Authorization'] ?? '';
    // 验证API token
    return $token === 'your-api-key';
}
if (!authenticate()) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}
switch ($endpoint) {
    case 'subscribe':
        if ($method === 'POST') {
            $data = json_decode(file_get_contents('php://input'), true);
            $result = $system->subscribe(
                $data['email'],
                $data['name'] ?? '',
                $data['groups'] ?? [],
                'api'
            );
            echo json_encode(['success' => $result]);
        }
        break;
    case 'unsubscribe':
        if ($method === 'POST') {
            $data = json_decode(file_get_contents('php://input'), true);
            $result = $system->unsubscribe($data['email']);
            echo json_encode(['success' => $result]);
        }
        break;
    case 'subscribers':
        if ($method === 'GET') {
            // 获取订阅者列表
            $page = $_GET['page'] ?? 1;
            $limit = $_GET['limit'] ?? 20;
            $subscribers = $system->getSubscribers($page, $limit);
            echo json_encode($subscribers);
        }
        break;
    case 'campaigns':
        if ($method === 'GET') {
            $campaigns = $system->getCampaigns();
            echo json_encode($campaigns);
        } elseif ($method === 'POST') {
            $data = json_decode(file_get_contents('php://input'), true);
            $id = $system->createCampaign($data);
            echo json_encode(['id' => $id]);
        }
        break;
    case 'stats':
        if ($method === 'GET') {
            $campaignId = $_GET['campaign_id'];
            $stats = $system->getCampaignStats($campaignId);
            echo json_encode($stats);
        }
        break;
    default:
        http_response_code(404);
        echo json_encode(['error' => 'Endpoint not found']);
}

部署建议

系统配置示例

<?php
// config/email_marketing.php
return [
    'db' => [
        'host' => 'localhost',
        'dbname' => 'email_marketing',
        'username' => 'root',
        'password' => '',
    ],
    'mail' => [
        'driver' => 'smtp',
        'host' => 'smtp.gmail.com',
        'port' => 587,
        'username' => 'your-email@gmail.com',
        'password' => 'your-password',
        'encryption' => 'tls',
    ],
    'default_sender_name' => 'Your Company',
    'default_sender_email' => 'noreply@yourcompany.com',
    'tracking_url' => 'https://yourdomain.com/track',
    'unsubscribe_url' => 'https://yourdomain.com/unsubscribe',
    'sending' => [
        'batch_size' => 50,
        'delay_between_emails' => 200000, // 微秒
        'max_attempts' => 3,
    ],
    'limits' => [
        'daily_send_limit' => 10000,
        'max_recipients_per_campaign' => 5000,
        'rate_limit_per_hour' => 1000,
    ],
];

维护计划

每日任务

  • 清理失败的邮件队列
  • 更新退信列表
  • 生成每日报告

每周任务

  • 清理无效/重复订阅者
  • 优化数据库索引
  • 检查发送限制

每月任务

  • 归档旧数据
  • 更新邮件模板
  • 审查发送策略

安全注意事项

  1. 邮件验证:使用双重确认(Double Opt-in)
  2. 速率限制:防止滥用和服务器过载
  3. 数据加密:敏感信息加密存储
  4. 退订机制:必须包含一键退订
  5. 认证授权:API使用token认证
  6. 日志记录:记录所有操作以便审计
  7. 防注入:所有用户输入需要验证和清理

这个系统设计提供了完整的邮件营销功能,可以根据实际需求进行调整和扩展,建议在生产环境前进行充分的测试,特别是大规模发送时的性能和稳定性测试。

抱歉,评论功能暂时关闭!