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
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
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
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:
$batchSize
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
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:
$maxRetries
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:
$staleLockMinutes
courier:process-drips claims enrollments (active → processing) 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.
$throttleMs
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:
$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:
With this set, a campaign view of welcome.md resolves to app/Emails/welcome.md.
See Email Templates for the full markdown workflow.
$testMode
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:
You'll see log entries like:
Environment-specific config
You can override any config value per-environment using CI4's .env file:
$captureRateLimit
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:
For high-traffic sites or shared hosting environments where many users may share an IP, you can raise the limit:
$honeypot
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:
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
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_eventsrows.
To enable:
$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):
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.
See Tracking — Bounce and complaint webhooks for the full setup walkthrough.