Developer guideIntegrations
Send email from Laravel
Send email with Couryo from Laravel using Mail::to()->send(), through the PHP SDK's couryo transport or over SMTP, with tags, queues and a signed webhook.
Two ways, both with Mail::to()->send() and the Mailables you already have:
- The PHP SDK's
couryotransport (recommended): talks to the API, returns the message ID and carries the Mailable's tags and metadata. - SMTP: no new dependency, only
.env.
Option 1: SDK transport#
composer require couryo/couryo-phpThe service provider registers itself (auto-discovery). Set .env:
MAIL_MAILER=couryo
COURYO_API_KEY=ck_test_...
COURYO_WEBHOOK_SECRET=whsec_...
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="Example"And add the mailer to config/mail.php:
'mailers' => [
// ...
'couryo' => [
'transport' => 'couryo',
],
],Done. Your existing code stays the same:
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
Mail::to($order->customer->email)->send(new OrderShipped($order));
// Queued (recommended, so the request does not wait):
Mail::to($order->customer->email)->queue(new OrderShipped($order));The Mailable's tags and metadata become Couryo tags and metadata, for filtering in the dashboard and in webhooks:
use Illuminate\Mail\Mailables\Envelope;
public function envelope(): Envelope
{
return new Envelope(
subject: "Your order {$this->order->number} has shipped",
tags: ['order'],
metadata: ['order_id' => (string) $this->order->id],
);
}To send on the marketing stream (with one-click unsubscribe), add the X-Couryo-Stream: marketing header in the Mailable's headers() method. Without it, the email is transactional.
Call the API directly#
For what Mail does not cover (batch, saved templates, domains, contacts, events and campaigns), inject the client:
use Couryo\Couryo;
public function store(Request $request, Couryo $couryo)
{
$user = User::create($request->validated());
$couryo->events->track([
'contact' => ['email' => $user->email, 'attributes' => ['name' => $user->name]],
'name' => 'user.signed_up',
], ['idempotency_key' => "signup-{$user->id}"]);
return redirect('/dashboard');
}Option 2: SMTP#
No package at all. On Laravel 11 or newer:
MAIL_MAILER=smtp
MAIL_SCHEME=smtp
MAIL_HOST=smtp.couryo.com
MAIL_PORT=587
MAIL_USERNAME=couryo
MAIL_PASSWORD=ck_live_...
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="Example"On port 587 the connection upgrades to TLS (STARTTLS) on its own. For 465, use MAIL_SCHEME=smtps. On Laravel 10 or older, replace MAIL_SCHEME with MAIL_ENCRYPTION=tls.
Over SMTP, the headers X-Couryo-Tags: kind=order and X-Couryo-Stream: marketing on the Mailable play the role of tags and stream. See SMTP.
Receive webhooks#
use Couryo\Exception\WebhookVerificationError;
use Couryo\Webhook;
use Illuminate\Http\Request;
Route::post('/webhooks/couryo', function (Request $request) {
try {
$event = Webhook::verify(config('couryo.webhook_secret'), $request->headers->all(), $request->getContent());
} catch (WebhookVerificationError $e) {
abort(400, 'invalid signature');
}
if ($event['type'] === 'bounced') {
// $event['data']['recipient'], $event['data']['explanation']
}
return response()->noContent();
});Take the route out of CSRF protection (the signature already protects it). On Laravel 11 or newer:
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: ['webhooks/couryo']);
})Test without sending#
With a ck_test_ key, emails are captured and show up in the dashboard without reaching anyone (test mode). In automated tests, keep using Mail::fake().
Common problems#
| Symptom | What to do |
|---|---|
Unsupported mail transport [couryo] |
the package was not loaded: run composer dump-autoload and php artisan config:clear |
domain_not_verified |
MAIL_FROM_ADDRESS must be on a verified domain |
535 5.7.8 over SMTP |
username other than couryo, or a key that is invalid, revoked or without the send scope |
Webhook returns 419 |
the route is still under CSRF protection |