開発者向け

プラグインを作る。回避策ではなく。

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 ソース一式が同梱されます。任意のクラスを書き換え、任意のサービスを上書きし、自分の namespace に fork してください。コードはあなたのもの、ルールもあなたのもの。Laravel 11+ ベースなので、すでに知っているフレームワークが重い処理を担います。

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

プラグインアーキテクチャ

Composer 形式のフォルダを storage/app/plugins/<vendor>/<name>/ に配置するだけでアプリが autoload します。ServiceProvider が routes / views / migrations / hooks を注入。有効化・無効化・削除をクリーンに行えます。

$ php artisan plugin:init myvendor/loyalty

トークン認証の REST API

キャンペーン、リスト、購読者、テンプレート、オートメーション用の CRUD エンドポイント。ドメインレベルの出来事ごとに発火する webhook イベント。SaaS フロントエンド、モバイルアプリ、他の Laravel サービスにそのまま組み込めます。プラグインが使う API と外部クライアントが使う API は同一 — 二級市民のエンドポイントはありません。

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

自社インフラ上でセルフホスト

あなたのサーバー、あなたの DB、あなたの購読者データ。ベンダーロックインなし。ライセンスは買い切り。送信コストは基盤の SMTP 実費のみ(例: Amazon SES は 1,000 通あたり $0.10)。PHP が動く環境ならどこへでも展開可能 — ベアメタル、Kubernetes、Forge、Laravel Vapor。スタックに応じて自由にスケールします。

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

プラグインで作れるもの

14 の拡張サーフェス。
すべてに実運用中の事例があります。

プラグインはディスク上に存在する自己完結型の Laravel パッケージです。独自の namespace、独自のデータベーステーブル、routes、views、controllers、models を持ち、include ハックではなく型付き Hook システム経由でコアと統合します。

ドライバと連携 · 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 popover
ページ単位のコンテンツスロット REGISTRY
page.{ctrl}.{action}.{slot}
page.maillist.show.body, page.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 モデル + DB テーブル BEHAVIOR
Plugin-isolated migrations
acelle/ai は 8 モデル / 12 migrations、vendor 接頭辞付きテーブル名で同梱
翻訳(18 ロケール) REGISTRY
add_translation_file
acelle/ai は 162 個の lang ファイル(18 × 9)を同梱

挙動とフロー · 3

Webhook リスナー / ドメインイベント EVENT
Hook::on(…)
customer_added, plan_changed, subscription_terminated
挙動の上書き BEHAVIOR
Hook::set / setIfEmpty / perform
dispatch_list_import_job — インポートロジックを丸ごと置き換え
フィルタチェーン FILTER
Hook::modify / filter
sidebar-menu-items、送信前のコンテンツ書き換え、リダイレクト URL

一つのプラグインで複数のサーフェスを組み合わせ可能 — acelle/ai は 14 のうち 7 を 1 パッケージで使用しています。

Hook システム

4 つの型付き拡張パターン。意図しない上書きなし。サイレント競合なし。

コアはプラグインのコードを import しません — 拡張ポイントを宣言するだけです。プラグインは listen して反応します。4 パターンそれぞれに明確な役割があり、競合は即座に例外を投げます。

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

ゼロから 8 コマンドで動くプラグインを作成。

scaffold がすべて生成します — Composer メタデータ、ServiceProvider、サンプルモデル、サンプル migration、サンプルルート、サンプルビュー。そこから先はあなたの自由に拡張できます。

  1. 1

    プラグインを scaffold

    標準フォルダ構造と inactive 状態の DB 行を生成します。

    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 を編集

    namespace と ServiceProvider を Composer に伝えます。

    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

    migration を記述

    プラグイン専用テーブル、vendor 接頭辞付きの名前。activate 時に実行、delete 時にロールバックします。

    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

    モデルを定義

    あなたの namespace 配下の標準的な 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

    controller と view を追加

    普通の Laravel controller — view namespace はプラグイン名です。

    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

    routes を宣言

    いつも使っている 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() で配線

    views と routes をロードし、activate_plugin_… にフックして有効化時に migration を実行します。

    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 にアクセス。migration が自動実行され、プラグインは指定の prefix で動作します。

    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

動くプラグインが完成しました。あとは機能に応じて models / controllers / hooks を好きなだけ追加してください。テストは tests/ に置き、php artisan test --testsuite='Plugin: myvendor/loyalty' で実行できます。正式な仕様はナレッジベースを参照してください。

プラグインライフサイクル

4 つの状態。予測可能な遷移。「本当に実行されたのか?」という謎はもうありません。

すべてのプラグインは正確に 4 つの状態のいずれか一つに存在します。現在の状態が、どの hook が発火するか、どの migration が実行済みか、次の遷移で何が起きるかを規定します。docs/plugin/SOURCE_OF_TRUTH.md に基づいたソース・グラウンディング。

STATE 01 register (inactive) プラグインフォルダが発見済み。index.json と DB 行は存在。Hook は発火していません。 STATE 02 active 全 hook が稼働。Routes はマウント済み。Migration 実行済み。activate_plugin_… は一度発火。 STATE 03 inactive (paused) フォルダはディスク上に残存。Hook は停止。Routes はアンマウント。DB テーブルは保持。 STATE 04 deleted (gone) フォルダ削除済み。$keepData = false なら migration をロールバック。DB 行も削除。 activate migrations 実行 deactivate データ保持 remove $keepData? re-activate activate hook は再実行されません
Register · inactive

発見済み、未稼働

プラグインフォルダ存在、index.json がインデックス化済み、DB 行は inactive。ServiceProvider の register() は実行済みですが、boot() の hook は有効化をクリックするまでゲートで遮断されています。

Active

稼働中、hook 発火中

初回の有効化で activate_plugin_<name> が一度発火 — ここで migration が実行されます。以降は無効化されるまで、REGISTRY / EVENT / FILTER / BEHAVIOR の全 hook が稼働します。

Inactive · paused

一時停止、データ保持

Routes はアンマウント、hook はゲートで遮断されますが、フォルダと DB テーブルは残存。再有効化しても migration や activate hook は再実行されない — 本番でのトグルが安全です。

Deleted

クリーンに削除

フォルダ削除、DB 行も消去。$keepData = false ならプラグインの migration がロールバック — vendor 接頭辞付きテーブルは drop されます。コアのテーブルには一切手を加えません。

実プラグインショーケース

acelle/ai — 1 つのプラグインフォルダで完結する AI サブシステム。

acelle/ai プラグインは完全な AI アシスタント層を同梱しています: 全ページに浮かぶエージェントチャットボックス、リッチテキストフィールドでの ✨ sparkle 書き換え、ナレッジベース基盤の coach ペルソナ(Deliverability, Segments, Campaigns, Forms)、5 つのルートにまたがる管理側の可観測性サーフェス。3 つのレイアウト hook 経由でマウントされ、コアの変更はゼロです。

acelle/ai プラグインの稼働イメージ — フローティング AcelleMail AI チャットボックスのバブル、ワンクリック書き換えの ✨ sparkle popover、そして AcelleMail UI 内にレンダリングされるプラグインの管理サイドバーグループ

同梱されているもの

  • フローティングチャットボックス — 通常のサポートモード + ツール呼び出し可能なオプトインのエージェントモード
  • Sparkle popover — リッチテキストフィールドでワンクリック書き換え / 拡張 / 簡略化
  • Coach ペルソナ — ドメイン認識型 LLM ヘルパー、画面ごとにスコープを限定
  • 管理側の可観測性 — 使用量、監査、会話、フィードバック、self-improve
  • エンジンの柔軟性 — BYO OpenAI、Anthropic、ローカル Ollama
  • プラグインのランディングページ /plugins/acelle/ai/dashboard

数字で見る

8
Eloquent モデル
12
Migrations
60+
Blade テンプレート
100+
Pest テスト
18 × 9
ロケール × lang ファイル
~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 hook — それだけです。加えてページ単位の注入、ライフサイクルイベントリスナー、/api/v1/ai/* 配下の名前空間化されたカスタム REST API。コアの変更はゼロで、プラグインフォルダはドロップインで配布可能です。

REST API

トークン認証エンドポイント。プラグインが使うのと同じ API。

REST API を使えば AcelleMail を自社の SaaS フロントエンド、モバイルアプリ、Laravel サービスに統合できます。プラグインが使う API と外部クライアントが使う API は同一 — 二級市民のエンドポイントはありません。

リソース

  • ListsGET/POST/PATCH/DELETE /api/v1/lists[/:uid]
  • Subscribers/api/v1/subscribers · tags · 登録/解除
  • Campaigns/api/v1/campaigns[/:uid] · run/pause/resume · ログ CSV
  • Automations/api/v1/automations[/:uid] · execute · api/call
  • Sending servers/api/v1/sending_servers[/:uid]
  • Customers (admin)/api/v1/customers[/:uid] · assign/change-plan · enable/disable
  • Subscriptions (admin)/api/v1/subscriptions[/:uid]

Webhook イベント

ライフサイクル(Admin → Webhooks で設定): new_customer, new_subscription, change_plan, cancel_subscription, terminate_subscription, automation_webhook
受信者単位のトラッキング(キャンペーン単位): open, click, unsubscribe

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"
}

オープンソースの理念

あなたのコードを永続的に。ブラックボックスなし、継続課金なし、namespace のスクワッティングなし。

暗号化なしのソース

サーバー上で動く PHP のすべての行を確認できます。ionCube も SourceGuardian も難読化もありません。カスタマイズは git diff で追跡、何でも grep で見つかります。

永続アップデート

2016 年以降の AcelleMail の全リリースが CodeCanyon のダウンロード履歴に残ります。必要なときに必要なものを取得できます。サブスク圧力なし。

あなたの namespace を永続的に

プラグインは MyVendor\MyPlugin\ 配下に存在し、Acelle\* コアから完全に隔離されます。Composer 経由でチームやマーケットプレイスに配布可能です。

買い切りライセンス

$80 (Regular) または $199 (SaaS 向け Extended) を一度支払うだけ。サブスクなし、購読者課金なし、メール課金なし。事業が成長しても計算式は変わりません。

リソースとコミュニティ

ドキュメント、サンプルプラグイン、サポートへの直通回線。

開発者向けドキュメント全文

ソースに根ざした 11 本の深掘り解説。必要なページへ直接ジャンプ:

ハブを見る →

プラグイン開発ガイド

ホスト側の 5 ファイル、boot-and-load フロー、4 つのライフサイクル状態、2 つの注入レイヤー — 他のドキュメントが前提とするメンタルモデル。

/developers/plugin-architecture →

REST API リファレンス

認証、エンドポイントカタログ、webhook ペイロード、エラー形状 — サンプル付きのネイティブランディングページ。

/api →

Hook システム — 4 つのパターン

REGISTRY / EVENT / BEHAVIOR / FILTER — プラグインが使う 4 つの拡張形状を、コアから grep した実呼び出し箇所、競合セマンティクス、6 つのアンチパターンとともに解説。

/developers/hook-system →

送信ドライバ — 完全プラグインパターン

コアを fork せずに新しい MTA バックエンドを提供。register_sending_server_driver 契約、9 つのケイパビリティマーカー、Postal プラグインで踏んだ 5 つの落とし穴。

/developers/sending-drivers →

プラグインショーケース — acelle/ai

正典級の複雑プラグインをエンドツーエンドで解説。8 モデル、14 migrations、18 ロケール、全 hook サーフェス、チャットボックス UI、加えて 4 ステップの学習レシピ。

/developers/showcase →

開発者サポート

Hook 契約やプラグインライフサイクルで詰まりましたか? メールでご連絡ください — 24 時間以内に返信します。

support@acellemail.com →
ドキュメントインデックス · CAT

11 本の深掘り解説をすべて見る →

基礎、実装、品質、リファレンス — 全ページを 1 箇所にまとめ、storage/app/plugins/acelle/ai/ と正典のプラグインドキュメントに対してソース・グラウンディング。

/developers →

開発者向け FAQ

購入前に開発者が実際に尋ねる質問。

コアのソースコードをアップデートを失わずに改変できますか? +

可能ですが、最もクリーンなパターンはプラグインです。プラグインはコアの namespace の外で動作し、アップグレードを自動的に生き延びます。コアを直接編集することもできますが、リリースごとに upstream の変更を手動でマージする必要があります。プラグインシステムはまさにそうした苦行を避けるために存在します。

プラグインは AcelleMail のアップグレードを生き延びますか? +

はい、設計上そうなっています。コアが hook ポイントを宣言し、プラグインが反応します。hook の名前とシグネチャが安定している限り(破壊的変更にはバージョンスタンプを付けます)、プラグインは動き続けます。リリースごとに AI プラグイン(acelle/ai)でテストしています。

プラグインを商用販売できますか? +

はい。Extended ライセンスは自作プラグインを自分のライセンスで配布する権利を付与します。プラグインは独自の namespace に存在し、そのコードは 100 % あなたのものです。複数のチームが社内プラグインエコシステム向けにプライベートマーケットプレイスを運営しています。

プラグインシステムは競合をどう扱いますか? +

REGISTRY hook はマージ(各プラグインが寄与)、EVENT hook はすべて発火(競合不可)、FILTER チェーンは累積、BEHAVIOR のみが「排他的」パターン — 2 つのプラグインが同じ挙動を主張しようとすると即座に例外が投げられます。サイレント上書きの不意打ちはありません。

プラグイン同士は通信できますか? +

はい、同じ Hook システム経由で可能です。プラグイン A がイベントを発火し、プラグイン B が listen できます。または public クラスを公開する — MyVendor\PluginA\Service は通常の PHP クラスなので、他と同じく import できます。acelle/ai プラグインは、他のプラグインがカスタム AI ツールを登録できるよう hook を公開しています。

開発者にとって Regular と Extended の違いは? +

Regular ($80) は単一ドメインでの自社利用をカバーします。Extended ($199) はエンドユーザーへの課金(SaaS としての再販)、ホワイトラベルクローンの配布、自作プラグインの販売権を追加します。両方とも同じソースコードを同梱しています。

プラグインの migration はメイン DB に触れますか? +

同じデータベースに書き込みますが、vendor 名で接頭辞を付けたテーブル(例: myvendor_loyalty_accounts)に書き込みます。Migration はプラグインの activate 時に実行され、$keepData = false での delete 時にロールバックします。コアテーブルへのリスクはゼロです。

PHP 以外の言語でプラグインを書けますか? +

プラグインのランタイムは PHP / Laravel です。ただしプラグインから何にでもシェルアウト可能 — Node サービス、Python ML、Go バイナリ、外部 HTTP API。複数のチームが外部ツールをラップするプラグインを提供しています。プラグインは統合シェル、重い処理はどこにでも置けます。

プラグインは AI サブシステムとどう連携しますか? +

acelle/ai は、他のプラグインがカスタム AI ツールを登録したり、エンジンを差し替えたり、可観測性レイヤーにフックしたりするための hook を公開します。契約は AI プラグインのソースを参照してください。あなたのプラグインから AI エージェントがコンテキストとして拾うイベントを発火することも可能です。

プラグインマーケットプレイスはありますか? +

現時点ではありません。プラグインは直接販売、GitHub、プライベートレジストリで配布されています。コミュニティマーケットプレイスはロードマップにあり、十分な数のサードパーティプラグインが揃った段階で立ち上げる予定です。

今日の午後、初めてのプラグインをリリースしましょう。

PHP ソース一式。永続アップデート。Hook システム。コピーできる実運用例。Extended ライセンス(商用プラグイン再配布 + SaaS 利用)は買い切り $199。

Extended ライセンスを購入 — $199 ライブデモを試す