Everyone

Writing Your First Plugin

Step-by-step guide to creating an Opterius Mail plugin, with a complete working example.

Last updated 2026-04-12
  • Accessing the App Container
  • Plugin with Database Table
  • Publishing Your Plugin
  • 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.md with: description, required .env keys, installation steps
    • Tag releases with semantic versions (v1.0.0)
    • Include a LICENSE file
    • Declare minimum Opterius Mail version compatibility in your README