Skip to content

Configuration

Courier's config file lives at app/Config/Courier.php after you publish it — a Config\Courier class extending the package's own. Every option below is inherited with a default that works out of the box, so redeclare only the ones you want to change.

Options

$fromName / $fromEmail

<?php
public string $fromName  = 'Acme Newsletter';
public string $fromEmail = 'hello@acme.com';

The default sender name and address for all outgoing emails. Individual campaigns can override these — these values are the fallback when a campaign doesn't specify its own.

$defaultLayout

<?php
public string $defaultLayout = 'Myth\Courier\Views\courier/layouts/default';

The email layout view used when a campaign doesn't have one set. Courier ships with a simple responsive layout at Views/courier/layouts/default.php. Point this at your own view to apply a custom look site-wide.

See Email Templates for how layouts work.

$trackingHost

<?php
public string $trackingHost = '';

The base URL used to build tracking pixel URLs, click redirect URLs, and unsubscribe links. Leave empty and Courier will use CI4's base_url() automatically. Set it if you're using a custom tracking domain:

<?php
public string $trackingHost = 'https://track.acme.com';

$batchSize

<?php
public int $batchSize = 200;

How many emails to send per courier:send-campaign or courier:process-drips command run. If a campaign has 10,000 contacts, the command works through them in chunks of 200 across successive cron runs.

Lower this if you're hitting rate limits; raise it if your email provider supports higher throughput.

$retryDelayMinutes

<?php
public int $retryDelayMinutes = 5;

How long to wait before retrying a drip step send that failed. When courier:process-drips can't deliver a step (the mailer returns false or throws), it pushes the enrollment's next_send_at forward by this many minutes instead of retrying immediately.

5 minutes is a sensible default — long enough to survive a brief ESP blip, short enough that recipients aren't delayed noticeably. For high-volume setups you may want a longer window to avoid hammering a struggling provider:

<?php
public int $retryDelayMinutes = 15;

.env override

courier.retryDelayMinutes = 10

$maxRetries

<?php
public int $maxRetries = 3;

Maximum number of send attempts per drip step before Courier gives up. After this many failures, the enrollment is marked failed and a courier_enrollment_failed event fires so your app can take action.

With the defaults (retryDelayMinutes = 5, maxRetries = 3), a step that keeps failing will be abandoned after roughly 15 minutes and 3 attempts. Raise this if you expect longer ESP outages:

<?php
public int $maxRetries = 5;

.env override

courier.maxRetries = 5

$staleLockMinutes

<?php
public int $staleLockMinutes = 15;

courier:process-drips claims enrollments (activeprocessing) before sending them, so an overlapping run can't send the same step twice. If a run crashes or is killed mid-batch, its claimed enrollments stay processing until this many minutes pass, at which point the next run reclaims them back to active and retries. See Overlapping runs.

Lower this if your batches normally finish in seconds and you want faster recovery from a crashed run; raise it if a single batch can legitimately take a while to send.

.env override

courier.staleLockMinutes = 10

$throttleMs

<?php
public int $throttleMs = 0;

Milliseconds to sleep between individual sends within a batch. 0 means no delay. Set this if your email provider has a per-second sending limit:

<?php
public int $throttleMs = 100; // ~10 emails/second max

$markdownPath

public string $markdownPath = '';

The base directory Courier uses when resolving markdown email files. Leave empty and it defaults to APPPATH (your app's app/ folder). Set an absolute path to load markdown files from a different location:

public string $markdownPath = APPPATH . 'Emails/';

With this set, a campaign view of welcome.md resolves to app/Emails/welcome.md.

See Email Templates for the full markdown workflow.

$testMode

<?php
public bool $testMode = false;

When true, Courier skips the actual mailer and logs what it would send instead. Use this in development or CI to verify your campaign setup without delivering real emails:

<?php
public bool $testMode = true;

You'll see log entries like:

[Courier] testMode: would send to ada@example.com subject "Welcome to Acme"

Environment-specific config

You can override any config value per-environment using CI4's .env file:

courier.testMode = true
courier.batchSize = 50

$captureRateLimit

<?php
public int $captureRateLimit = 15;

Maximum number of POST submissions allowed per IP address per minute on the /courier/capture endpoint. Requests over the limit receive a 429 Too Many Requests response — a redirect back with a courier_errors flash message for standard form submissions, or a JSON error for AJAX requests.

Set to 0 to disable rate limiting entirely:

<?php
public int $captureRateLimit = 0; // no limit

For high-traffic sites or shared hosting environments where many users may share an IP, you can raise the limit:

<?php
public int $captureRateLimit = 60;

.env override

courier.captureRateLimit = 30

$honeypot

<?php
public bool $honeypot = true;

When true, Courier renders a hidden courier_hp field in forms generated by courier_form() and courier_form_open(). Real users never see or fill this field. Bots that blindly populate all inputs get silently rejected — Courier returns a success response without saving anything.

Set to false to opt out:

<?php
public bool $honeypot = false;

Custom form layouts

If you build your own form markup and post to /courier/capture, the honeypot check still runs server-side. To avoid false rejections, make sure your form doesn't submit a courier_hp field — or disable the honeypot if you can't control the submitted fields.

$trackIpAddress

<?php
public bool $trackIpAddress = false;

When true, the IP address of the recipient who clicked a tracked link is stored in the metadata column of courier_events.

Default is false. IP addresses are personal data under GDPR and CCPA. Only enable this setting if:

  • Your privacy policy discloses that click IP addresses are collected and processed, and
  • You have a data-retention policy that covers courier_events rows.

To enable:

<?php
public bool $trackIpAddress = true;

.env override

courier.trackIpAddress = true

$webhookDriver

<?php
public string $webhookDriver = '';

The fully-qualified class name of a WebhookDriverInterface implementation that handles incoming ESP webhook notifications (bounces, complaints, subscription confirmations). Leave empty to disable the POST /courier/webhook endpoint — it returns 400 when no driver is configured.

Courier ships with a driver for AWS SES (routed through SNS):

<?php
public string $webhookDriver = \Myth\Courier\Webhooks\Drivers\SesDriver::class;

To use a different ESP, implement WebhookDriverInterface and point this at your class.

CSRF exemption required

The webhook endpoint receives machine-to-machine POST requests from your ESP. You must add 'courier/webhook' to $CSRFExcludeURIs in app/Config/Security.php, otherwise all webhook calls will be rejected.

.env override

courier.webhookDriver = \Myth\Courier\Webhooks\Drivers\SesDriver::class

See Tracking — Bounce and complaint webhooks for the full setup walkthrough.