承接 timefrontiers/php-mailer 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

timefrontiers/php-mailer

Composer 安装命令:

composer require timefrontiers/php-mailer

包简介

Email sending, templating, mailing lists, and delivery queuing for the TimeFrontiers ecosystem

README 文档

README

Email sending, templating, bulk queuing, and delivery logging for the TimeFrontiers ecosystem.

Supports Mailgun and native SMTP out of the box. Additional drivers can be added by implementing MailDriverInterface. Attachment support is provided via timefrontiers/php-file (persisted files) or raw filesystem paths (transient).

Requirements

  • PHP 8.2+
  • MySQL 8.0+ / MariaDB 10.6+
  • timefrontiers/php-file ^1.0
  • symfony/mailer ^7.0

Installation

composer require timefrontiers/php-mailer

Database

Run schema/schema.sql against your target database to create all required tables:

mailer_profiles     — verified sender identities
email_templates     — reusable HTML/Markdown shells
mailing_lists       — named recipient groups
emails              — core email records (DRAFT → OUTBOX → SENT)
email_recipients    — TO / CC / BCC / Reply-To per email or list
email_attachments   — maps emails to php-file File records
email_log           — per-recipient delivery tracking
email_queue         — bulk personalized send queue (see Queue)

Note: email_queue references mailer_profiles via FK. Run the full schema in order, or use the two-step approach documented in the schema file comments.

Bootstrap

Call Config::set() once at application startup before using any mailer class:

use TimeFrontiers\Mailer\Config;
use TimeFrontiers\Mailer\Driver\MailgunConfig;
use TimeFrontiers\Mailer\Driver\SmtpConfig;

// Mailgun
Config::set(new Config(
    dbName:     'msgservice',
    mailServer: 'https://mail.example.com',
    driver: new MailgunConfig(
        domain: 'mg.example.com',
        apiKey: 'key-xxxxxxxxxxxx',
        region: 'us',   // 'us' or 'eu'
    ),
));

// — or — native SMTP
Config::set(new Config(
    dbName:     'msgservice',
    mailServer: 'https://mail.example.com',
    driver: new SmtpConfig(
        host:       'smtp.example.com',
        port:       587,
        username:   'user@example.com',
        password:   'secret',
        encryption: 'tls',   // 'tls' | 'ssl' | 'none'
    ),
));

Registering templates in Config

Associate template codes and token variable lists with named message types. This lets Email::make() resolve the correct template automatically.

Config::set(new Config(
    dbName:     'msgservice',
    mailServer: 'https://mail.example.com',
    driver:     new MailgunConfig(...),
    templates: [
        'default' => [
            'templateCode' => '42912345678',          // email_templates.code
            'replaceVars'  => ['user-name', 'user-surname'],
        ],
        'order-confirm' => [
            'templateCode' => '42999999999',
            'replaceVars'  => ['order-id', 'total', 'user-name'],
        ],
    ],
));

Sending an email

Email::make() signature

Email::make(
    SQLDatabase              $conn,
    Profile                  $sender,
    string                   $subject,
    string                   $body,
    string                   $user         = 'SYSTEM',
    ?string                  $message_type = 'default',
    int|string|Template|null $template     = null,
    ?DriverConfigInterface   $driver       = null,
    bool                     $log_body     = true,
): Email
Parameter Description
$sender Profile instance — the From address.
$message_type Used to look up Config::templates for template + token defaults. Pass null to skip Config template lookup entirely (no template, no replaceVars seeding).
$template Explicit override: pass an int id, string code, or Template instance. null = use config lookup.
$driver Transport override. null = use Config::get()->driver.
$log_body false → body saved as ***redacted*** in DB (use for OTP / sensitive codes). Email is still delivered correctly.

Basic example

use TimeFrontiers\Mailer\Email;
use TimeFrontiers\Mailer\Profile;
use TimeFrontiers\Mailer\RecipientType;

// Resolve a sender profile (find-or-create by address)
$sender = Profile::resolve($conn, 'hello@example.com', 'Example', 'Team');

// Create a draft — template resolved from Config['default']
$email = Email::make(
    $conn,
    $sender,
    'Welcome to Example, %{user-name}!',
    '<p>Hi %{user-name}, thanks for joining.</p>',
    $currentUserCode,   // platform user code or 'SYSTEM'
    'default',          // message_type — matches Config::templates key
);

// Add recipients (no $conn needed — uses internally stored connection)
$email->addRecipient('alice@example.com', RecipientType::TO);
$email->addRecipient(['email' => 'bob@example.com', 'name' => 'Bob'], RecipientType::CC);
$email->addRecipient('replies@example.com', RecipientType::REPLY_TO);

// Send — bare-key token map applied to subject + body
$email->send([
    'user-name'    => 'Alice',
    'user-surname' => 'Smith',
]);

Token replacements

Tokens in subject and body use the %{key} syntax. Pass bare keys (without %{}) to send():

// Body: "<p>Hi %{user-name} %{user-surname},</p>"
$email->send([
    'user-name'    => 'Alice',
    'user-surname' => 'Smith',
]);
// Renders: "<p>Hi Alice Smith,</p>"

Replacements are merged on top of the replaceVars defaults seeded from Config::templates. Per-call values always win.

Sensitive content — $log_body = false

// OTP or password-reset email — code must not be stored in the database
$email = Email::make(
    $conn, $sender,
    'Your verification code',
    '<p>Your code is: <strong>%{otp-code}</strong>. Expires in 10 minutes.</p>',
    $userCode,
    'default',
    null,     // template
    null,     // driver
    false,    // log_body — body saved as ***redacted*** in DB
);
$email->addRecipient($recipientEmail, RecipientType::TO);
$email->send(['otp-code' => '123 4567 8']);

Templates

Templates are outer HTML shells. The email body is injected via the %{body} token at render time. Both %{body} (new) and %{message} (legacy) are supported for backward compatibility.

use TimeFrontiers\Mailer\Email\Template;

// Create and persist a new template
$template = Template::make(
    $conn,
    'Default Shell',
    '<html><body style="font-family:sans-serif">%{body}</body></html>',
    $userCode,
);

// Look up an existing template by id or code
$template = Template::findById(42);           // by int id
$template = Template::findById('42912345678'); // by string code

// Attach to an email explicitly (overrides Config lookup)
$email->setTemplate($template);

Attachments

use TimeFrontiers\File\File;

// Persisted — backed by timefrontiers/php-file; row written to email_attachments
$file = File::load($conn, $fileCode);
$email->attach($file);

// Transient — raw filesystem path; not stored in email_attachments
$email->attachRaw('/var/invoices/inv-001.pdf', 'application/pdf', 'Invoice.pdf');

Deferred delivery (OUTBOX queue)

// Move to OUTBOX and create pending EmailLog entries
$email->queue($conn, $sender, priority: 3);

// In your cron / queue runner — load OUTBOX emails and dispatch
$pending = Email::findBySql(
    'SELECT * FROM :db:.:tbl: WHERE `folder` = ?', ['outbox']
);
foreach ($pending as $e) {
    $e->send();
}

Bulk personalized sending — Email\Queue

Email\Queue is designed for newsletters, campaigns, and any batch send where each recipient receives a personalized copy. The template shell is applied once at queue-creation time; per-recipient token replacements are applied at dispatch time.

use TimeFrontiers\Mailer\Email\Queue;

$queue = Queue::make(
    $conn,
    $sender,
    'Hi %{user-name} — your monthly update',
    '<p>Dear %{user-name} %{user-surname},<br>Here is your update...</p>',
    'default',    // message_type — resolves template from Config
);

// Add recipients with their per-recipient token values
$queue->addRecipient('john@doe.com', [
    'user-name'    => 'John',
    'user-surname' => 'Doe',
]);
$queue->addRecipient(['name' => 'Jane', 'email' => 'jane@doe.com'], [
    'user-name'    => 'Jane',
    'user-surname' => 'Doe',
]);
$queue->addRecipient('plain@example.com', []);

// Dispatch immediately
$sent = $queue->dispatch();   // returns count of successfully sent recipients

// — or — leave as 'pending' and let the cron runner handle it
Queue::processNext($conn, $sender, limit: 50);

Queue recipients are not persisted to email_recipients — they are stored as JSON inside email_queue.recipients. This keeps the queue lightweight for large batches.

Delivery log

Each send() creates one EmailLog row per TO recipient:

use TimeFrontiers\Mailer\Log\EmailLog;

$log = EmailLog::loadById($conn, $logId);
$log->markRead();   // recipient opened the email

Folder states

Value Constant Description
draft Folder::DRAFT Not yet queued or sent
outbox Folder::OUTBOX Queued for deferred delivery
sent Folder::SENT All recipients dispatched

Adding a new driver

  1. Create a typed config class implementing DriverConfigInterface:
final class SendGridConfig implements DriverConfigInterface {
    public function __construct(public readonly string $apiKey) {}
    public function driverName(): string { return 'sendgrid'; }
    public function toDsn(): string { return "sendgrid+api://{$this->apiKey}@default"; }
}
  1. Create the driver class implementing MailDriverInterface.
  2. Add one arm to DriverFactory::fromConfig().

Code prefixes

Entity Prefix Example
Email 421 421394827163058
Template 429 429847392016453
Mailing list 218 218736402918374

Migration

Run the migration script inside the database that holds your existing tables:

mysql -u root -p your_database < schema/migrate_lnk_to_tf.sql

What the migration does, in order:

  • Adds id BIGINT UNSIGNED AUTO_INCREMENT PK to emails, email_templates, and mailing_lists (which used code CHAR(14) as PK); sequential ids are back-filled before the PK swap
  • Widens code from CHAR(14) to CHAR(15) across all tables
  • Renames mailing_lists.titlename; drops description
  • Adds _updated to email_templates and mailing_lists
  • Migrates email_recipients: replaces char-code columns email / mlist with integer FK columns email_id / mlist_id; back-fills via JOIN; tightens type to ENUM
  • Migrates email_attachments: replaces email (code) with email_id (int FK); renames fidfile_id
  • Migrates email_log: replaces email (code) with email_id (int FK); renames sendersender_id and recipientrecipient_id
  • Adds is_md, template_id, sender_id to emails; back-fills template_id; drops template, header, origin, replace_pattern, thread

License

MIT — see LICENSE.

timefrontiers/php-mailer 适用场景与选型建议

timefrontiers/php-mailer 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 23 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 27 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「mailer」 「php」 「template」 「email」 「smtp」 「mailgun」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 timefrontiers/php-mailer 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 timefrontiers/php-mailer 我们能提供哪些服务?
定制开发 / 二次开发

基于 timefrontiers/php-mailer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 23
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 34
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-27