Writing Your First Plugin
This guide walks through creating a plugin from scratch. The example plugin appends a "Sent via Opterius Mail" footer to every outgoing message — a simple but complete demonstration of the plugin system.
Step 1 — Create the Plugin Directory
mkdir -p /opt/opterius-mail/plugins/my-footer
The directory name becomes the plugin's identifier. Use lowercase letters, hyphens, and numbers only.
Step 2 — Create plugin.php
Create /opt/opterius-mail/plugins/my-footer/plugin.php:
<?php
namespace Plugins\MyFooter;
use App\Plugins\Plugin;
class MyFooterPlugin extends Plugin
{
/**
* Plugin metadata.
*/
public string $name = 'my-footer';
public string $version = '1.0.0';
public string $description = 'Appends a footer to every outgoing message.';
/**
* Called once at application boot. Register your hooks here.
*/
public function boot(): void
{
$this->addHook(Plugin::HOOK_SEND, [$this, 'appendFooter']);
}
/**
* Hook handler: modify the outgoing message before it is sent.
*
* @param array $message Mutable message data
* @return array Modified message data
*/
public function appendFooter(array $message): array
{
$footerText = config('my-footer.text', 'Sent via Opterius Mail');
// Append to HTML body
if (!empty($message['body_html'])) {
$message['body_html'] .= sprintf(
'<br><br><hr style="border:none;border-top:1px solid #eee;margin:16px 0">'
. '<p style="color:#999;font-size:12px">%s</p>',
e($footerText)
);
}
// Append to plain text body
if (!empty($message['body_text'])) {
$message['body_text'] .= "\n\n-- \n{$footerText}";
}
return $message;
}
}
Step 3 — (Optional) Add a Config File
If your plugin has configurable settings, create a config file at plugins/my-footer/config.php:
<?php
return [
/*
|--------------------------------------------------------------------------
| Footer text
|--------------------------------------------------------------------------
| The text appended to outgoing messages. Supports plain text only
| (HTML version is wrapped automatically).
*/
'text' => env('MY_FOOTER_TEXT', 'Sent via Opterius Mail'),
];
To make this config available to the application, register it in the boot() method:
public function boot(): void
{
// Merge the plugin's config into the application
$this->mergeConfig(__DIR__ . '/config.php', 'my-footer');
$this->addHook(Plugin::HOOK_SEND, [$this, 'appendFooter']);
}
Then in .env:
MY_FOOTER_TEXT="Sent via Opterius Mail | example.com"
Step 4 — Verify Detection
cd /opt/opterius-mail
php artisan mail:plugins:list
You should see my-footer listed as active.
Step 5 — Test It
Send a test email via webmail. Open the sent message and check that the footer was appended to the body.
All Available Hook Constants
| Constant | Trigger | Receives | Should return |
|---|---|---|---|
Plugin::HOOK_COMPOSE |
Compose view about to render | array $data (to, cc, subject, body, mode) |
Modified $data array |
Plugin::HOOK_SEND |
Message about to be sent via SMTP | array $message (full message data) |
Modified $message array, or null to cancel send |
Plugin::HOOK_LOGIN |
User successfully authenticated | array $user (email, name, ip) |
Not used (return value ignored) |
Plugin::HOOK_MESSAGE_VIEW |
Message fetched, before view render | array $message |
Modified $message array |
Plugin::HOOK_SETTINGS |
Settings page rendering | array &$sections |
Modified $sections array |
Cancelling a Send
Return null from a HOOK_SEND handler to silently prevent the message from being sent:
public function blockExternalDomains(array $message): ?array
{
foreach ($message['to'] as $recipient) {
if (!str_ends_with($recipient['email'], '@example.com')) {
// Log the cancellation and return null to prevent send
logger()->warning('Blocked outgoing mail to external recipient', $recipient);
return null;
}
}
return $message;
}
Adding a Settings Section
public function addSettingsSection(array &$sections): void
{
$sections[] = [
'title' => 'Footer Settings',
'view' => __DIR__ . '/views/settings-section.blade.php',
'plugin' => $this->name,
];
}
Accessing the App Container
From within a plugin:
// Get a config value
$value = app('config')->get('my-footer.text');
// Get the authenticated user's email
$email = auth('imap')->user()?->email;
// Resolve a service from the container
$db = app(\Illuminate\Database\ConnectionInterface::class);
Plugin with Database Table
If your plugin needs its own table, create a migration file:
// plugins/my-footer/migrations/2026_04_12_000000_create_footer_log_table.php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
public function up(): void
{
Schema::create('footer_log', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('footer_log');
}
};
Register the migration path in boot():
public function boot(): void
{
$this->loadMigrationsFrom(__DIR__ . '/migrations');
$this->addHook(Plugin::HOOK_SEND, [$this, 'appendFooter']);
}
Users install the table by running php artisan migrate after installing the plugin.
Publishing Your Plugin
Follow these conventions when sharing your plugin publicly:
- Repository name:
opterius-mail-plugin-{name}(e.g.,opterius-mail-plugin-footer) - Include a
README.mdwith: description, required.envkeys, installation steps - Tag releases with semantic versions (
v1.0.0) - Include a
LICENSEfile - Declare minimum Opterius Mail version compatibility in your README