PHPackages                             myth/courier - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. myth/courier

ActiveLibrary[Framework](/categories/framework)

myth/courier
============

A CodeIgniter 4 package

v1.0.0-beta.2(1mo ago)51[1 PRs](https://github.com/lonnieezell/courier/pulls)MITPHPPHP ^8.2CI passing

Since May 20Pushed 1mo agoCompare

[ Source](https://github.com/lonnieezell/courier)[ Packagist](https://packagist.org/packages/myth/courier)[ RSS](/packages/myth-courier/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (7)Versions (4)Used By (0)

myth/courier
============

[](#mythcourier)

Email campaigns and drip sequences for CodeIgniter 4.

**[Full documentation →](https://lonnieezell.github.io/courier/)**

- **Blast campaigns** — send a one-time email to a segment, tag-filtered audience, or all contacts
- **Drip sequences** — multi-step automated email sequences with configurable delays between steps
- **Contact management** — subscribe/unsubscribe, status tracking, tags, and custom fields
- **Audience segmentation** — target contacts by segment or tag
- **Email tracking** — open pixels, click-wrapped links, bounce and complaint logging
- **CLI automation** — spark commands for scheduled delivery and drip processing

Requirements
------------

[](#requirements)

- PHP 8.2+
- CodeIgniter 4.7+

Installation
------------

[](#installation)

```
composer require myth/courier
```

Run the package migrations:

```
php spark migrate --all
```

### Register tracking routes

[](#register-tracking-routes)

The package ships a `Config/Routes.php` that registers the tracking endpoints automatically via CI4's module discovery. Enable it by adding `'routes'` to your `app/Config/Modules.php`:

```
// app/Config/Modules.php
public $aliases = ['routes'];
```

That's it — the following routes are then available with no further configuration:

RoutePurpose`GET /courier/open/{token}`Open-tracking pixel (returns 1×1 GIF)`GET /courier/click/{token}`Click redirect (tracks click, redirects to target URL)`GET /courier/unsubscribe/{token}`One-click unsubscribe`POST /courier/capture`Signup form capture (used by `courier_form()`)**Custom prefix or manual control:** If you want a different URL prefix or to disable the routes entirely, keep `'routes'` out of `$aliases` and add the group yourself in `app/Config/Routes.php`:

```
$routes->group('my-prefix', ['namespace' => 'Myth\Courier\Controllers'], static function ($routes): void {
    $routes->get('open/(:segment)',        'CourierController::open/$1');
    $routes->get('click/(:segment)',       'CourierController::click/$1');
    $routes->get('unsubscribe/(:segment)', 'CourierController::unsubscribe/$1');
    $routes->post('capture',              'CourierController::capture');
});
```

Configuration
-------------

[](#configuration)

Publish the config file:

```
php spark publish:config Courier
```

Edit `app/Config/Courier.php`:

PropertyTypeDefaultDescription`$fromName``string``''`Default sender display name`$fromEmail``string``''`Default sender address`$defaultLayout``string`bundled defaultCI4 view path for the email wrapper layout`$trackingHost``string``''`Custom tracking domain (e.g. `https://track.yoursite.com`); leave empty to use `base_url()``$batchSize``int``200`Max emails sent per CLI run`$throttleMs``int``0`Milliseconds to sleep between individual sends (`0` = no throttle)`$testMode``bool``false`When `true`, logs emails instead of sending themQuick Start
-----------

[](#quick-start)

### Subscribe a contact

[](#subscribe-a-contact)

```
$contactService = service('contactService');

// Basic subscribe
$contact = $contactService->subscribe([
    'email'      => 'jane@example.com',
    'first_name' => 'Jane',
]);

// Subscribe with tags
$contact = $contactService->subscribe($data, tags: ['newsletter', 'vip']);

// Subscribe and immediately enroll in a drip campaign
$contact = $contactService->subscribe($data, tags: ['trial'], dripCampaignId: $drip->id);
```

### Send a blast campaign

[](#send-a-blast-campaign)

```
$campaignService = service('campaignService');

$campaign = $campaignService->create([
    'name'       => 'May Newsletter',
    'subject'    => 'What\'s new in May',
    'view'       => 'emails/newsletter',
    'from_name'  => 'ACME Co.',
    'from_email' => 'news@acme.com',
    'tag_filter' => ['newsletter'],  // or 'segment_id' => 3
]);

$campaignService->schedule($campaign->id, new DateTime('+1 hour'));
// Courier CLI will pick it up on the next run
```

### Set up a drip sequence

[](#set-up-a-drip-sequence)

```
$campaignService = service('campaignService');
$dripService     = service('dripService');

// 1. Create the campaign
$campaign = $campaignService->create([
    'name'    => 'Welcome Drip',
    'subject' => 'Welcome!',
    'type'    => 'drip_sequence',
]);

// 2. Add steps
$campaignService->addDripStep($campaign->id, [
    'view'        => 'emails/welcome_step1',
    'subject'     => 'Welcome to the family',
    'delay_hours' => 0,   // send immediately on enroll
]);
$campaignService->addDripStep($campaign->id, [
    'view'        => 'emails/welcome_step2',
    'subject'     => 'How are you settling in?',
    'delay_hours' => 48,
]);

// 3. Enroll a contact
$dripService->enroll(contactId: $contact->id, campaignId: $campaign->id);
```

Segmentation
------------

[](#segmentation)

Courier supports two audience targeting strategies.

### Tag filtering

[](#tag-filtering)

Tag-filter campaigns send to all contacts who have ALL of the specified tags:

```
$campaignService->create([
    'name'       => 'Trial Users',
    'tag_filter' => ['trial', 'active'],  // stored as JSON
    // ...
]);
```

### Segment rules

[](#segment-rules)

Segments let you save reusable audience definitions. The `rules` column is a JSON array of conditions matched against contact fields:

```
[
    {"field": "custom_fields->plan", "op": "=",    "value": "pro"},
    {"field": "status",              "op": "=",    "value": "subscribed"},
    {"field": "subscribed_at",       "op": ">=",   "value": "2024-01-01"}
]
```

`match_mode` controls whether all rules must match (`AND`) or any rule may match (`OR`).

Create segments via `SegmentService`:

```
$segmentService = service('segmentService');
$contacts = $segmentService->resolve($segment->id);        // array of ContactDTO
$count    = $segmentService->previewCount($segment->id);   // int
```

Template Authoring
------------------

[](#template-authoring)

Email templates are standard CI4 views. The view path is stored on each campaign or drip step and resolved via `TemplateService::render()`.

### Available variables

[](#available-variables)

Inside a campaign body view:

VariableDescription`$contact``ContactDTO` — email, first\_name, last\_name, custom\_fields, etc.`$campaign``CampaignDTO` — name, subject, from\_name, from\_email`$unsubscribeUrl`Pre-generated one-click unsubscribe URLInside a layout view:

VariableDescription`$content`The rendered body view HTML`$contact`Same `ContactDTO` as above`$campaign`Same `CampaignDTO` as above`$unsubscribeUrl`One-click unsubscribe URL### Example body view

[](#example-body-view)

```
// app/Views/emails/welcome.php
Hi ,
Thanks for joining!
