223

Product support

Visit this product's website for support.

Categories

Inherent.Automation

Email automation plugin for OctoberCMS (from v3 onward). Manages subscriber lists, double opt-in, multi-step email sequences, open/click tracking, and unsubscribe (RFC 8058 compliant).

Replaces paid email marketing automation systems like system.io, mailerlite, brevo, etc...

Integration with RainLab.User, Responsiv.Campaign, MelonCart, Mall

Installation via Command Line

php artisan plugin:install Inherent.Automation

Inherent.Automation

Email automation plugin for OctoberCMS (from v3 onward). Manages subscriber lists, double opt-in, multi-step email sequences, open/click tracking, and unsubscribe (RFC 8058 compliant).

Replace email automation like system.io, mailerlite, brevo, etc...


Requirements

  • OctoberCMS v3
  • PHP 8.2+
  • A configured mail driver (SMTP, Mailgun, SES…)

Installation

  1. After installation from the marketplace : Add the scheduler entry to your crontab. Use the full PHP binary path — cron does not inherit your shell PATH: -e then add the line
    * * * * * "/path/to/your/root/project/php" /path/to/artisan schedule:run >> /dev/null 2>&1

The scheduler runs automation:process every minute to dispatch queued emails.


Backend user guide

Subscriber lists

Automation > Lists

A list groups subscribers under a name and optional sender identity. Each list has a unique code (auto-generated from the name, editable).

Mark one list as default so the subscribe form falls back to it when no explicit list is configured.

Field Description
Name Display name
Code Unique slug, used in templates and API
From name Sender name for emails sent to subscribers of this list
From email Sender address. Falls back to mail.from if empty
Is default Used by the frontend component when no list is specified

Subscribers

Automation > Subscribers

A subscriber is identified by email address. The subscriber record carries a global status that acts as a high-water mark:

Status Meaning
pending Signed up, confirmation email sent, not yet confirmed on any list
confirmed Confirmed on at least one list
unsubscribed Opted out
bounced Set manually for hard bounces

Subscription status is tracked per list, not globally. The same subscriber can be confirmed on list A and pending on list B — both the global status and the per-list pivot status coexist independently. The Lists tab on the subscriber form shows the pivot status, subscription date, and unsubscription date for each list.

Adding a list from the backend (via the Add button) confirms the subscription immediately, bypassing double opt-in.

The backend form page shows all list memberships and the full email history for that subscriber.

Email templates

Automation > Email Templates

Reusable email bodies written in HTML + Twig. Templates define subject, HTML body, and optional sender overrides (from name, from email).

Available Twig variables:

Variable Value
{{ subscriber.email }} Subscriber email address
{{ subscriber.first_name }} First name
{{ subscriber.last_name }} Last name
{{ subscriber.full_name }} First + last name
{{ unsubscribe_url }} Unique unsubscribe link (per-email tracking code)
{{ site_url }} Site root URL
{{ site_name }} App name from config
{{ subject }} Email subject

The layout file (views/mail/email_layout.htm) wraps all rendered bodies. Edit it to change the global email frame. It receives the same variables plus {{ body }}.

Automations

Automation > Automations

An automation is a sequence of steps triggered by an event.

Field Description
Name Internal label
Status draft (never runs), active (processes queue), inactive (paused)
Trigger type See below
Trigger lists Visible for subscribe_to_list: which lists trigger this automation
Event name Visible for custom_event: a code developers pass to AutomationEngine::trigger()

Trigger types:

Type Description
subscribe_to_list Fires when a subscriber confirms subscription to one of the selected lists
custom_event Fires when a developer calls AutomationEngine::trigger('event-name', $subscriber)
manual Never fires automatically — enqueue via code with AutomationEngine::enqueueForSubscriber()

Steps

Each automation has ordered steps. A step = one email sent after a delay (in days).

Field Description
Name Optional internal label
Delay (days) Days after the trigger (0 = immediately on next worker run)
Template Optional saved email template
Subject Overrides the template subject if set
HTML body Overrides the template body if set
From name Overrides list/global sender if set
From email Overrides list/global sender if set

Priority rule: inline step fields override the template, which overrides the list defaults, which override mail.from.

When a trigger fires, the engine enqueues one item per step, each scheduled at trigger_time + cumulative delay.

Queue

Automation > Queue

Read-only view of all queue items. Useful for debugging delivery issues.

Status Meaning
pending Waiting to be dispatched
sent Dispatched and logged
failed Worker threw an exception
cancelled Subscriber unsubscribed before dispatch

Reports

Automation > Reports

Global index — Total confirmed subscribers, new (30 days), emails sent (30 days), open rate (30 days). Tables for all automations and all lists with key stats.

Per-automation (/reports/automation/{id}) — Total sent / opened / clicked / pending. Per-step breakdown with open rate and click rate. 50 most recent send events.

Per-list (/reports/list/{id}) — Confirmed subscriber count, new today, emails sent and open rate across all automations linked to this list. Table of linked automations. 20 most recent confirmed subscribers.

Per-subscriber (/reports/subscriber/{id}) — Subscription history across all lists with statuses and timestamps. Full email history: automation, step, subject, sent time, opened time, click count, status.


Frontend component

automationSubscribeForm

Attach to any CMS page or layout to render a subscription form with double opt-in.

[automationSubscribeForm]
listId = 1
allowListChoice = 0
successMessage = "Thank you! Check your email to confirm."
Property Type Default Description
listId dropdown Subscribe to this list. Falls back to the default list if empty
allowListChoice checkbox false Renders checkboxes so the visitor can choose which lists to join
selectableLists set Which lists to display when allowListChoice is on. Leave empty to show all lists
successMessage string "Thank you! Please check your email to confirm your subscription." Message shown after successful signup

selectableLists is only used when allowListChoice is enabled. It lets you restrict the choice to a curated subset without exposing every list.

The form submits via AJAX. On success it replaces the form with the success message. Validation errors appear inline.

The default template (components/subscribeform/default.htm) is unstyled HTML. Override it from your theme. The updatable inner content lives in components/subscribeform/_form.htm — override that partial when you only need to restyle the form and success state.


Developer guide

Architecture overview

── subscribe_to_list trigger ──────────────────────────────────────────────────

SubscribeForm (component)
    └── SubscriberManager::signup()
            sends confirmation email

TrackingHandler::confirmSubscription()
    └── SubscriberManager::confirm()
            updates pivot status → fires inherent.automation.subscriber.confirmed

Event: inherent.automation.subscriber.confirmed
    └── AutomationEngine::onSubscriberConfirmed()
            enqueues steps → AutomationQueue

── custom_event trigger ───────────────────────────────────────────────────────

Any plugin
    └── AutomationEngine::trigger('event-name', $subscriber)
            looks up active automations by event name
            enqueues steps → AutomationQueue (subscriber_list_id = null)

── dispatch (both paths) ──────────────────────────────────────────────────────

Scheduler (every minute)
    └── php artisan automation:process
            └── AutomationWorker::process()
                    dispatches due queue items
                    records AutomationLog with tracking code

Public routes

Method URL Action
GET automations/confirm/{token} Confirms a list subscription (double opt-in)
GET automations/unsubscribe/{token} Unsubscribes via tracking code
POST automations/unsubscribe/{token} One-Click unsubscribe (RFC 8058)
GET automations/o/{code}.png Open tracking pixel
GET automations/c/{code}?url=… Click tracking redirect

Events

Event Payload Fired when
inherent.automation.subscriber.confirmed ($subscriber, $list) Subscription confirmed via double opt-in or direct backend confirm
inherent.automation.subscriber.unsubscribed ($subscriber, $list\|null) Subscriber opts out. $list is null for custom_event triggered automations
\Event::listen('inherent.automation.subscriber.confirmed', function ($subscriber, $list) {
    // $list is always a SubscriberList instance here
});

\Event::listen('inherent.automation.subscriber.unsubscribed', function ($subscriber, $list) {
    // $list may be null if the automation had no list context (custom_event trigger)
});

SubscriberManager API

use Inherent\Automation\Classes\SubscriberManager;

// Sign up from a form — creates subscriber + pivot, sends confirmation email
$subscriber = SubscriberManager::signup($email, [1, 3], [
    'first_name' => 'Marie',
    'last_name'  => 'Dupont',
], $request->ip());

// Sign up from a RainLab User object
$subscriber = SubscriberManager::signupFromUser($user, [1]);

// Confirm directly — bypasses double opt-in (backend/admin action)
SubscriberManager::confirmDirect($subscriber, $list);

// Unsubscribe by tracking code (called by the unsubscribe route)
SubscriberManager::unsubscribeByTrackingCode($trackingCode);

signup() is idempotent: calling it twice with the same email only sends a new confirmation if the subscriber had previously unsubscribed.

Custom event trigger

Fire a named automation from any plugin:

use Inherent\Automation\Classes\AutomationEngine;

// Pass a Subscriber instance
AutomationEngine::trigger('order.placed', $subscriber);

// Pass an email string — throws \InvalidArgumentException if not found in DB
AutomationEngine::trigger('order.placed', 'customer@example.com');

In the backend, create an automation with trigger "When a custom event is triggered" and set the event name to order.placed. All active automations matching that slug will enqueue their steps for the subscriber.

The subscriber must already exist in the database. The method does not auto-create subscribers — the caller controls consent and opt-in.

Unsubscribe behaviour — with no list context, clicking the unsubscribe link cancels all pending queue items for that subscriber in the triggered automation. The subscriber's global status and other list memberships are unaffected.

Example (MelonCart / Mall)

// After a completed order
$subscriber = Subscriber::where('email', $order->customer_email)->first();

if ($subscriber) {
    AutomationEngine::trigger('order.placed', $subscriber);
}

// After a loyalty milestone
if ($customer->completedOrdersCount() >= 5) {
    AutomationEngine::trigger('loyalty.vip', $subscriber);
}

RainLab.User integration

When RainLab.User is installed, set rainlab_user_default_lists in the plugin config to auto-subscribe new registrations:

// config/config.php
    'rainlab_user_default_lists' => [1, 2],

New users are signed up with a confirmation email sent to those list IDs.

Confirmation redirect

After a subscriber clicks the confirmation link, the plugin redirects to the URL defined by confirm_redirect in config/inherent/automation.php:

// config/inherent/automation.php
return [
    'confirm_redirect' => env('AUTOMATION_CONFIRM_REDIRECT', '/email-confirmation'),
];

Set AUTOMATION_CONFIRM_REDIRECT in your .env to override without touching the config file:

AUTOMATION_CONFIRM_REDIRECT=/merci-de-votre-inscription

Defaults to /email-confirmation if the variable is absent.

Running automations manually

php artisan automation:process

Picks up all queue items with scheduled_at <= now() and status = pending, processed in batches of 50.

Extending with new trigger types

  1. Add the key to Automation::getTriggerTypeOptions().
  2. Call AutomationEngine::enqueueForSubscriber($automation, $subscriber, $list) from wherever the trigger fires.
\Event::listen('your.custom.event', function ($user) {
    $subscriber = Subscriber::where('email', $user->email)->first();
    if (!$subscriber) return;

    Automation::active()
        ->where('trigger_type', 'your_trigger')
        ->with('steps')
        ->get()
        ->each(fn ($automation) => AutomationEngine::enqueueForSubscriber($automation, $subscriber));
});

Responsiv.Campaign integration

When the Responsiv Campaign plugin is installed, Inherent.Automation registers its subscriber lists as recipient groups inside Campaign. No configuration required — the integration activates automatically on boot. Set the environment variable AUTOMATION_CAMPAIGN_INTEGRATION=true to enable this automatic integration. Per default, it is set to false by config.

What it does

Each confirmed-subscriber list appears in Campaign's message composer under a group named Automation : <list name>. Marketers can target these segments directly when sending a one-time newsletter or bulk campaign, without duplicating subscribers between the two plugins.

Only subscribers with a confirmed status on the list are exposed.

How it works

The plugin listens to two Campaign events:

Event Role
responsiv.campaign.listRecipientGroups Declares each Automation list as a named recipient group
responsiv.campaign.getRecipientsData Returns confirmed subscriber emails, first names and last names for the requested group

Groups are keyed automation_list_{id} to avoid collisions with other Campaign recipient sources.

Enabling / disabling

The integration is off by default when Campaign is installed. Enable/Disable it via .env:

#AUTOMATION_CAMPAIGN_INTEGRATION=true
AUTOMATION_CAMPAIGN_INTEGRATION=false

Or directly in config/inherent/automation.php:

'campaign_integration' => false,

Requirements

  • responsiv/campaign installed and active

Permissions

Permission key Description
inherent.automation.manage_automations Create/edit automations and steps, view queue
inherent.automation.manage_subscribers Manage subscribers and lists, view reports
inherent.automation.manage_templates Create/edit email templates

Changelog

1.0.4

  • automationSubscribeForm: new selectableLists property (type set). When allowListChoice is on, only the selected lists are shown to the visitor. Leave empty to keep the previous behaviour (all lists).
  • automationSubscribeForm: fixed confirmation message not appearing after form submission. The AJAX update now returns the rendered inner partial directly from the handler, avoiding the onRun / re-render conflict that reset subscribed to false.
  • Responsiv.Campaign integration: when Campaign is installed, Automation subscriber lists are automatically registered as recipient groups in Campaign's message composer (confirmed subscribers only). Disable via AUTOMATION_CAMPAIGN_INTEGRATION=false or campaign_integration in config.

1.0.3

  • New trigger type custom_event: fire automations from any plugin via AutomationEngine::trigger('event-name', $subscriber).
  • Fixed unsubscribeByTrackingCode() for automations with no list context (subscriber_list_id = null): pending queue items are now cancelled per-automation instead of silently ignored.
  • Reports: per-automation report (step breakdown, open/click rates), per-list report (linked automations, recent subscribers), per-subscriber report (full history with subjects and click counts). Fixed open rate query on list report (was using whereJsonContains on a non-existent JSON field).
  • Subscriber form: Lists tab now shows per-list pivot status, subscription date, and unsubscription date.
1.0.2

Add inline email fields on automation steps (subject, content_html, from_name, from_email). email_template_id is now optional.

Jun 29, 2026

1.0.1

Add trigger lists relation table (replaces JSON trigger_config for list_ids).

Jun 29, 2026

1.0.0

First version of Inherent Automation plugin.

Jun 29, 2026