面向开发者

构建插件。而不是绕过去的临时方案。

AcelleMail 是基于 Laravel 11+ 的邮件营销平台,附完整未加密 PHP 源代码 — 与一套类型化的 Hook 系统,让您的插件可以新增发送驱动、支付网关、AI、自定义 UI,甚至 REST API。无需 fork 核心。

今天就在运行的真实插件

acelle/ai athena/evs rencontru/postal acelle/console
myvendor/loyalty/ PLUGIN SOURCE composer.json 240 B routes.php 1.2 KB src/ ServiceProvider.php entry Controllers/ Models/ database/migrations/ resources/ views/ lang/ index.json auto $ php artisan plugin:init … HOOK SYSTEM no core touches ACELLEMAIL Laravel 11+ · PHP 8.3+ layout.head.assets layout.body.close admin.sidebar.groups register_sending_* page.{ctrl}.{slot} customer_added sidebar-menu-items dispatch_*_job + many more…

开发者为何选择 AcelleMail

建立在您已熟悉的框架之上。
用您已熟悉的模式扩展。

开源 PHP / Laravel

每份授权均附完整未加密 PHP 源代码。修改任意类、覆盖任意服务、fork 到您自己的命名空间 — 您的代码,您的规则。基于 Laravel 11+,您熟悉的框架承担重活。

$ grep -r "protected function" vendor/acelle/

插件架构

将一个符合 Composer 结构的文件夹放入 storage/app/plugins/<vendor>/<name>/,应用会自动加载。ServiceProvider 注入路由、视图、迁移、hooks — 激活、停用、删除干净利落。

$ php artisan plugin:init myvendor/loyalty

Token 鉴权的 REST API

对营销活动、名单、订阅者、模板、自动化的 CRUD 端点。每一个领域级事件都有 webhook。可直接接入 SaaS 前端、移动应用或其他 Laravel 服务。插件调用的 API 与外部客户端调用的 API 完全相同 — 不存在二等公民端点。

GET /api/v1/campaigns · POST /api/v1/lists

自托管在您的基础设施

您的服务器、您的数据库、您的订阅者数据。无供应商锁定。一次性支付授权;只为底层 SMTP 成本付费 (例如 Amazon SES 每千封 $0.10)。可部署在任何 PHP 运行环境 — 物理机、Kubernetes、Forge、Laravel Vapor。按您技术栈的扩展方式扩展。

· PHP 8.3+ · MySQL / MariaDB · Redis (optional)

插件能构建什么

14 个扩展接入面。
每一个今天都有真实生产案例。

插件是一个自包含的 Laravel 包,存放在磁盘上,有自己的命名空间、数据库表、路由、视图、控制器、模型 — 并通过类型化的 Hook 系统与核心集成,而不是 include 黑科技。

驱动与集成 · 4

发送服务器 / 驱动 REGISTRY
register_sending_server_driver
rencontru/postal — Postal MTA 驱动以单一插件即插即用
邮件验证 / 送达率 REGISTRY
REGISTRY + FILTER
athena/evs — 验证子系统含管理 UI
支付网关 REGISTRY
REGISTRY
内置的 Stripe / PayPal / Braintree / Paystack / Razorpay / Coinbase 展示该模式
AI / 聊天机器人 / 助手 REGISTRY
REGISTRY + EVENT
acelle/ai — 聊天框 + sparkle + 可观测性

UI 与页面 · 5

自定义 UI 注入 REGISTRY
layout.head.assets / layout.body.before_close / admin.sidebar.groups
acelle/ai 聊天框 + sparkle 弹层
页面级内容槽位 REGISTRY
page.{ctrl}.{action}.{slot}
page.maillist.show.bodypage.campaign.index.sidebar
管理页面 ROUTE
Laravel routes
/rui/admin/ai-usage/rui/admin/ai-audit/rui/admin/ai-conversations
面向客户的页面 ROUTE
Laravel routes
/plugins/acelle/ai/dashboard
REST API ROUTE
Routes + api.access_token
acelle/ai 中的 /api/v1/ai/*

数据与生命周期 · 2

自定义 Eloquent 模型 + 数据库表 BEHAVIOR
Plugin-isolated migrations
acelle/ai 附 8 个模型 / 12 份迁移,带 vendor 前缀的表名
翻译 (18 种语言) REGISTRY
add_translation_file
acelle/ai 附 162 份语言文件 (18 × 9)

行为与流程 · 3

Webhook 监听器 / 领域事件 EVENT
Hook::on(…)
customer_addedplan_changedsubscription_terminated
行为覆盖 BEHAVIOR
Hook::set / setIfEmpty / perform
dispatch_list_import_job — 完全替换导入逻辑
过滤器链 FILTER
Hook::modify / filter
sidebar-menu-items、内容发送前修改、跳转 URL

单一插件可以混用任意接入面 — acelle/ai 在一个包里用了 14 个中的 7 个。

Hook 系统

四种类型化扩展模式。无惊喜覆盖。无静默冲突。

核心从不引入插件代码 — 它只声明扩展点。插件监听并响应。四种模式,每一种角色清晰。冲突立即抛出。

1

REGISTRY add() + collect()

插件向一个列表贡献条目。核心问"谁有发送驱动?"您的插件回答"我有一个"。


Hook::add('register_sending_server_driver', fn () => [
    'type'   => 'myvendor-foo',
    'driver' => \MyVendor\Foo\Driver::class,
]);


$drivers = Hook::collect('register_sending_server_driver');
2

EVENT on() + fire()

插件对某事发生做出响应。返回值被丢弃。


Hook::on('customer_added', function ($customer) {
    LoyaltyPoints::award($customer, 100, 'welcome');
});


Hook::fire('customer_added', [$customer]);
3

BEHAVIOR set() + perform()

插件完全替换一段核心逻辑。同一行为只能由一个插件认领 — 冲突立即抛出。


Hook::set('dispatch_list_import_job',
    fn ($list, $f) => new MyFasterImportJob($list, $f));


Hook::setIfEmpty('dispatch_list_import_job',
    fn ($list, $f) => new DefaultImportJob($list, $f));
$job = Hook::perform('dispatch_list_import_job', [$list, $f]);
dispatch($job);
4

FILTER modify() + filter()

插件通过一串回调链转换某个值。每个回调接收当前值,返回修改后的值。


Hook::modify('sidebar-menu-items', function (array $items) {
    $items[] = [
        'label' => 'Loyalty',
        'url'   => route('lp.dashboard'),
    ];
    return $items;
});


$menu = Hook::filter('sidebar-menu-items', $defaultMenu);

5 分钟 Hello World

从零到可运行的插件,八条命令。

脚手架会创建一切 — Composer 元数据、ServiceProvider、示例模型、示例迁移、示例路由、示例视图。从这里开始,如何成长由您决定。

  1. 1

    生成插件脚手架

    创建标准目录布局 + 一行未激活的数据库记录。

    bash
    $ php artisan plugin:init myvendor/loyalty
      ✓ Created storage/app/plugins/myvendor/loyalty/
      ✓ DB row inserted  (status: inactive)
      ✓ index.json + ServiceProvider autoloaded
  2. 2

    编辑 composer.json

    告诉 Composer 您的命名空间与 ServiceProvider。

    json
    {
      "name": "myvendor/loyalty",
      "description": "Award points on signup",
      "version": "1.0.0",
      "autoload":  { "psr-4": { "MyVendor\\Loyalty\\": "src/" } },
      "extra":     { "laravel": { "providers": [
        "MyVendor\\Loyalty\\ServiceProvider"
      ] } }
    }
  3. 3

    编写迁移

    插件独有的表,带 vendor 前缀的表名。激活时运行,删除时回滚。

    php
    Schema::create('myvendor_loyalty_accounts', function (Blueprint $t) {
        $t->id();
        $t->foreignId('customer_id')->constrained()->cascadeOnDelete();
        $t->integer('points')->default(0);
        $t->timestamps();
    });
  4. 4

    定义模型

    在您自有命名空间下的标准 Eloquent 模型。

    php
    namespace MyVendor\Loyalty\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Account extends Model
    {
        protected $table    = 'myvendor_loyalty_accounts';
        protected $fillable = ['customer_id', 'points'];
    }
  5. 5

    添加控制器与视图

    普通的 Laravel 控制器 — 视图命名空间就是您的插件名。

    php
    namespace MyVendor\Loyalty\Controllers;
    
    use Acelle\Http\Controllers\Controller;
    
    class DashboardController extends Controller
    {
        public function index()
        {
            return view('loyalty::dashboard', [
                'accounts' => \MyVendor\Loyalty\Models\Account::orderByDesc('points')->take(10)->get(),
            ]);
        }
    }
  6. 6

    声明路由

    与您已使用的 Route::group 相同。由 ServiceProvider::boot() 加载。

    php
    Route::group([
        'middleware' => ['web', 'auth'],
        'namespace'  => '\MyVendor\Loyalty\Controllers',
        'prefix'     => 'plugins/myvendor/loyalty',
    ], function () {
        Route::get('/',          'DashboardController@index')->name('lp.dashboard');
        Route::get('/{account}', 'DashboardController@show');
    });
  7. 7

    在 ServiceProvider::boot() 中接好一切

    加载视图 + 路由;挂入 activate_plugin_… 以在激活时迁移。

    php
    public function boot(): void
    {
        $this->loadViewsFrom (__DIR__.'/../resources/views', 'loyalty');
        $this->loadRoutesFrom(__DIR__.'/../routes.php');
    
        Hook::on('activate_plugin_myvendor/loyalty', fn () =>
            \Artisan::call('migrate', [
                '--path'  => 'storage/app/plugins/myvendor/loyalty/database/migrations',
                '--force' => true,
            ])
        );
    
        Hook::on('customer_added', fn ($customer) =>
            Models\Account::create(['customer_id' => $customer->id, 'points' => 100])
        );
    }
  8. 8

    从管理后台激活

    前往 /rui/admin/plugins。迁移会自动运行。插件就在其前缀下生效。

    bash
    ✓ activated  myvendor/loyalty  v1.0.0
      → migrations: 1 ran (myvendor_loyalty_accounts)
      → routes:     2 registered under /plugins/myvendor/loyalty
      → hooks:      2 listening (activate, customer_added)
      → status:     active
    
    → visit /plugins/myvendor/loyalty/  ← live

您现在拥有一个可运行的插件。按功能需要添加任意数量的模型、控制器、hook。测试放在 tests/;用 php artisan test --testsuite='Plugin: myvendor/loyalty' 运行。规范契约在知识库中。

插件生命周期

四种状态。可预测的转换。没有"它跑了吗?"的悬念。

每个插件正好处于四种状态之一。当前状态决定哪些 hook 触发、哪些迁移已运行,以及下一次转换会发生什么。源代码契约见 docs/plugin/SOURCE_OF_TRUTH.md

STATE 01 register (未激活) 插件文件夹已发现。index.json + 数据库记录存在。Hooks 未触发。 STATE 02 active 所有 hooks 在线。路由已挂载。迁移已运行。activate_plugin_… 已触发一次。 STATE 03 inactive (已暂停) 文件夹仍在磁盘上。Hooks 暂停。路由已卸载。数据库表完好。 STATE 04 deleted (已移除) 文件夹已移除。若 $keepData = false 则迁移回滚。数据库记录已删除。 activate 运行迁移 deactivate 保留数据 remove $keepData? re-activate activate hook 不会重新触发
Register · 未激活

已发现,未运行

插件文件夹存在,index.json 已索引,数据库记录显示 inactive。ServiceProvider 的 register() 已运行,但 boot() 中的 hooks 被门控关闭,直到您点击激活。

Active

在线,hooks 触发中

首次激活会触发一次 activate_plugin_<name> — 迁移就在这里运行。此后所有 REGISTRY / EVENT / FILTER / BEHAVIOR hooks 都在线,直到停用。

Inactive · 已暂停

已暂停,数据完好

路由卸载、hooks 门控关闭,但文件夹 + 数据库表保留。重新激活不会重跑迁移或 activate hook — 生产环境切换安全。

Deleted

干净移除

文件夹移除、数据库记录消失。若 $keepData = false,插件迁移回滚 — 带 vendor 前缀的表被删除。核心表从不被触碰。

真实插件示例

acelle/ai — 单一插件文件夹内的完整 AI 子系统。

acelle/ai 插件交付完整的 AI 助手层:每一页上的智能体聊天框、富文本字段上的 ✨ Sparkle 改写、基于知识库的教练角色 (送达率、细分、营销活动、表单),以及横跨 5 条路由的管理员可观测性接入面。它通过 3 个布局 hook 挂载。零核心改动。

acelle/ai 插件运行中 — 浮动的 AcelleMail AI 聊天框气泡,搭配用于一键改写的 ✨ Sparkle 弹层,以及插件的管理员侧栏分组渲染在 AcelleMail UI 内

它都交付什么

  • 浮动聊天框 — 纯支持模式 + 可选开启的工具调用智能体模式
  • Sparkle 弹层 — 每个富文本字段上的一键改写 / 扩写 / 简化
  • 教练角色 — 领域感知的 LLM 助手,按页面限定作用域
  • 管理员可观测性 — 用量、审计、对话、反馈、自我改进
  • 引擎灵活性 — 自带 OpenAI、Anthropic 或本地 Ollama
  • 插件落地页位于 /plugins/acelle/ai/dashboard

数字看板

8
Eloquent 模型
12
迁移文件
60+
Blade 模板
100+
Pest 测试
18 × 9
语言 × 语言文件
~35
CSS + JS 文件

它如何挂载 — 3 个 hook

acelle/ai plugin → CSS / JS assets Hook::add( 'layout.head.assets', fn()=>...); → Chatbox bubble Hook::add( 'layout.body.before_close' ,fn()=>...); → AI sidebar group Hook::add( 'admin.sidebar.groups', fn()=>...); Self-contained. AcelleMail core <head> + acelle-ai-chatbox.css </body> (just before) + <div class="ai-chatbox"> Admin sidebar AI · Settings AI · Usage AI · Conversations

3 个布局 REGISTRY hooks — 仅此而已。再加上按页面的注入、生命周期事件监听器,以及命名于 /api/v1/ai/* 的自定义 REST API。零核心改动;插件文件夹即拷即用。

REST API

Token 鉴权的端点。插件用的是同一套 API。

使用 REST API 将 AcelleMail 集成进您自己的 SaaS 前端、移动应用或 Laravel 服务。插件调用的 API 与外部客户端调用的 API 完全相同 — 不存在二等公民端点。

资源

  • 名单GET/POST/PATCH/DELETE /api/v1/lists[/:uid]
  • 订阅者/api/v1/subscribers · tags · 订阅/退订
  • 营销活动/api/v1/campaigns[/:uid] · 运行/暂停/恢复 · 日志 CSV
  • 自动化/api/v1/automations[/:uid] · execute · api/call
  • 发送服务器/api/v1/sending_servers[/:uid]
  • 客户 (管理员)/api/v1/customers[/:uid] · 分配/更改套餐 · 启用/禁用
  • 订阅 (管理员)/api/v1/subscriptions[/:uid]

Webhook 事件

生命周期 (在管理后台 → Webhooks 配置):new_customernew_subscriptionchange_plancancel_subscriptionterminate_subscriptionautomation_webhook
按收件人的追踪 (按营销活动):openclickunsubscribe

完整 API 参考 →
# Create a list
$ curl -X POST \
    https://your-acelle.example.com/api/v1/lists \
    -H "Authorization: Bearer $ACELLE_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Newsletter",
      "from_email": "hi@example.com",
      "from_name": "Hi Team",
      "subject": "Weekly digest"
    }'

# Add a subscriber
$ curl -X POST \
    .../api/v1/lists/:uid/subscribers \
    -H "Authorization: Bearer $ACELLE_TOKEN" \
    -d '{"EMAIL":"alice@example.com"}'

# Send a campaign
$ curl -X POST \
    .../api/v1/campaigns/:uid/send \
    -H "Authorization: Bearer $ACELLE_TOKEN"

# Receive a webhook
POST /your-webhook-endpoint
{
  "event": "campaign.sent",
  "campaign_uid": "abc123",
  "list_uid": "xyz789",
  "sent_at": "2026-05-06T12:34:56Z"
}

开源理念

您的代码,永久属于您。没有黑盒、没有订阅费、没有命名空间抢占。

未加密源代码

运行在您服务器上的每一行 PHP。无 ionCube、无 SourceGuardian、无混淆。可 git diff 您的定制;可 grep 查找任何内容。

终身更新

自 2016 年起的每一个 AcelleMail 版本都在您的 CodeCanyon 下载历史中。想要什么时候拉什么时候拉。无订阅压力。

您的命名空间,永久属于您

插件位于 MyVendor\MyPlugin\ — 与 Acelle\* 核心完全隔离。可通过 Composer 分发给您的团队或市场。

一次性授权

$80 (普通版) 或 $199 (面向 SaaS 的扩展版)。无订阅、不按订阅者、不按邮件。随着您增长,算式从不变化。

资源与社区

文档、示例插件,以及直通支持的渠道。

完整开发者文档

十一篇源代码契约的深度解析。直达您需要的页面:

浏览中心 →

插件开发指南

宿主侧的五个文件、引导加载流程、四种生命周期状态、两层注入 — 其余文档默认采用的心智模型。

/developers/plugin-architecture →

REST API 参考

鉴权、端点目录、webhook 载荷、错误形态 — 原生着陆页含示例。

/api →

Hook 系统 — 四种模式

REGISTRY / EVENT / BEHAVIOR / FILTER — 插件使用的四种扩展形态,附从核心 grep 出的真实调用点、冲突语义,以及六种反模式。

/developers/hook-system →

发送驱动 — 完整插件模式

不 fork 核心也能交付全新的 MTA 后端。register_sending_server_driver 契约、九个能力标记,以及 Postal 插件的五个陷阱。

/developers/sending-drivers →

插件示例 — acelle/ai

端到端走查的规范化复杂插件。八个模型、十四份迁移、十八种语言、所有 hook 接入面、聊天框 UI,以及四步学习配方。

/developers/showcase →

开发者支持

卡在 hook 契约或插件生命周期问题上?给我们发邮件 — 典型 24 小时内回复。

support@acellemail.com →
文档索引 · CAT

浏览全部 11 篇深度解析 →

基础、构建、质量、参考 — 每一页都在一处,与 storage/app/plugins/acelle/ai/ 及规范化插件文档对齐源代码契约。

/developers →

开发者 FAQ

开发者在购买前真正会问的问题。

我可以修改核心源代码而不丢失更新吗? +

可以,但最干净的模式是插件。插件生活在核心命名空间之外,可在每次升级中自动保留。直接修改核心可行,但每次发布都需手动合并上游变更。插件系统的存在正是为了避免这种琐碎工作。

插件在 AcelleMail 升级中能保留吗? +

能,这是设计如此。核心声明 hook 点;插件响应。只要 hook 名与签名保持稳定 (我们会为破坏性变更打版本标记),您的插件就一直能用。我们在每次发布时都会测试 AI 插件 (acelle/ai)。

我可以商业销售插件吗? +

可以。扩展授权让您可以按自有授权分发自己的插件。插件生活在自己的命名空间中;那段代码 100% 归您所有。多个团队为其内部插件生态运营私有市场。

插件系统如何处理冲突? +

REGISTRY hook 会合并 (每个插件都贡献);EVENT hook 全都会触发 (不可能冲突);FILTER 链会累积;BEHAVIOR 是唯一"独占"的模式 — 若两个插件试图认领同一行为,会立即抛异常。没有静默覆盖的意外。

插件之间能对话吗? +

能,通过同一套 Hook 系统。插件 A 可以触发事件供插件 B 监听。或暴露一个公共类 — MyVendor\PluginA\Service 就是一个普通 PHP 类,可像其他类一样被引入。acelle/ai 插件就暴露了 hook 让其他插件注册自定义 AI 工具。

对于开发者,普通版与扩展版的区别是什么? +

普通版 ($80) 覆盖您自己在单域名上的使用。扩展版 ($199) 增加向最终用户收费的权利 (作为 SaaS 转售)、分发白标克隆,以及销售您自己的插件。两者交付的源代码相同。

插件迁移会影响主数据库吗? +

它们写入同一份数据库,但表名以您的 vendor 名为前缀 (如 myvendor_loyalty_accounts)。迁移在插件激活时运行,在 $keepData = false 的插件删除时回滚。对核心表零风险。

可以用 PHP 之外的语言写插件吗? +

插件运行时是 PHP/Laravel。但您的插件可以 shell 出去调用任何东西 — Node 服务、Python ML、Go 二进制、外部 HTTP API。多个团队都交付了封装外部工具的插件。插件是集成外壳;重活可以放在任何地方。

插件如何与 AI 子系统交互? +

acelle/ai 暴露 hook 供其他插件注册自定义 AI 工具、切换引擎或挂入可观测性层。阅读 AI 插件源代码以了解契约。您的插件也可以触发事件,AI 智能体会将其作为上下文捕获。

有插件市场吗? +

目前还没有。今天,插件通过直接销售、GitHub 或您自己的私有注册中心分发。一旦第三方插件足够多到值得维护时,社区市场就在路线图上。

今天下午就交付您的第一个插件。

完整 PHP 源代码。终身更新。Hook 系统。可供拷贝的真实生产案例。$199 一次性获取扩展授权 (商业插件再分发 + SaaS 使用)。

购买扩展授权 — $199 试用在线演示