cboxdk / laravel-relay-client
Cbox Relay client for Laravel: publish events, send mail and push, judge content, read inbound mail and verify Relay's webhooks, with an in-memory fake for tests.
Requires
- php: ^8.4
- illuminate/bus: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/mail: ^12.0 || ^13.0
- illuminate/notifications: ^12.0 || ^13.0
- illuminate/queue: ^12.0 || ^13.0
- illuminate/routing: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- symfony/mailer: ^7.2 || ^8.0
- symfony/mime: ^7.2 || ^8.0
Requires (Dev)
- larastan/larastan: ^3.12
- laravel/pint: ^1.32
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^4.0 || ^5.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
The Cbox Relay client for Laravel. Cbox Relay delivers webhooks, mail and push for Cbox products; this package is how a Laravel app talks to its API.
- Publish events.
Relay::publish()queues a job that calls Relay after commit and retries it, with an idempotency key so a retry publishes once. - Webhook endpoints. Declare event types; register, pause, rotate, ping and replay endpoints; read events and what became of each delivery.
- Mail. Sending domains, the suppression list,
Relay::sendMail(), and arelaymail transport soMAIL_MAILER=relayworks. - Web Push. The scope's key, browser subscriptions,
Relay::push(), and a Laravel notification channel. - Inbound mail. Claim addresses, read received mail, erase it.
- The judge. Score a mail or a template without sending it.
- Inbound webhooks. Sources that take a provider's webhooks for a scope.
- Erasure. Publish with a
subject;Relay::eraseSubject()forgets that person. - Webhooks from Relay. A
relay.webhookmiddleware that verifies a delivery, and typed objects for Relay's own events. - Scopes, whoami, the delivery log, typed errors, and a fake for your tests.
The client covers Relay's API. What the service behind it runs is Relay's own: its API guides say the mail servers are not running yet, so today no mail leaves Relay and none arrives. See Relay's own status, and BUILD-STATUS.md for what this package has and has not verified.
Documentation: docs/index.md. Start with the quickstart.
Install
composer require cboxdk/laravel-relay-client
RELAY_URL=https://api.cboxrelay.com RELAY_KEY=rlk_live_… RELAY_SCOPE=workspace-42 # optional: the scope every call acts for
With RELAY_URL or RELAY_KEY empty every call throws RelayNotConfigured. Publish
the config with php artisan vendor:publish --tag=relay-config; every key is in the
configuration reference.
Relay::publish() needs a queue worker.
Use
use Cbox\Relay\Client\Facades\Relay; // One scope per workspace, under your own id. Safe to call every time. Relay::scopes()->put($workspace->id, name: $workspace->name); $relay = Relay::forScope($workspace->id); // Webhooks out $relay->eventTypes()->declare(['order.paid', 'order.refunded']); $endpoint = $relay->endpoints()->register('https://customer.example/hooks', ['order.*']); $endpoint->secret; // given once $relay->publish('order.paid', ['id' => $order->id], subject: "contact_{$contact->id}"); // queued, after commit, retried $relay->publishNow('order.paid', ['id' => $order->id])->id; // one HTTP call, now $relay->eraseSubject("contact_{$contact->id}")->removed; // forget one person's events // Webhooks in $relay->sources()->register('stripe', SourceScheme::Stripe, $providerSecret)->url; // Mail $relay->sendMail(new OutgoingMail(new MailAddress('hello@acme.dk', 'Acme'), ['ann@example.com'], 'Your order', text: 'Thanks.')); // Web Push $relay->push($subscriptionId, ['title' => 'Your order shipped']); // The judge $relay->judge(html: $html, subject: 'Welcome')->score('phishing'); // Inbound mail $relay->inbound()->list();
Most calls act for one scope. Without one (forScope() or RELAY_SCOPE) they throw
RelayScopeRequired before anything is sent; with a scope key, set
RELAY_SCOPE_KEY=true.
Relay's errors are typed: RelayUnauthorized, RelayForbidden, RelayNotFound,
RelayConflict (->type() names Relay's reason, e.g. mail_not_held), RelayTooLarge, RelayInvalid (->errors), RelayRateLimited
(->retryAfter) and RelayUnavailable, all extending RelayException.
Laravel's mailer
// config/mail.php 'mailers' => ['relay' => ['transport' => 'relay']],
MAIL_MAILER=relay
Receiving Relay's webhooks
Route::post('/hooks/relay', function (Request $request) { $event = IncomingWebhook::from($request)->event(); // MailBounced, PushExpired, … return response()->noContent(); })->middleware('relay.webhook'); // verifies X-Cbox-Signature with RELAY_WEBHOOK_SECRET
Testing
use Cbox\Relay\Client\Facades\Relay; $relay = Relay::fake()->acceptAnyScope(); // publish() records at once: no queue, no HTTP $this->post("/orders/{$order->id}/pay"); $relay->assertPublished('order.paid', fn (NewEvent $event) => $event->payload['id'] === $order->id); $relay->assertMailSent(fn (OutgoingMail $mail) => $mail->to === ['ann@example.com']); $relay->assertPushed($subscriptionId);
See Testing.
Development
composer qa # pint, phpstan (max), pest, licenses, audit, sbom
MIT. See LICENSE.md and SECURITY.md.