Looking to hire Laravel developers? Try LaraJobs

laravel-relay-client maintained by cboxdk

Description
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.
Last update
2026/11/12 09:31 (dev-main)
License
Downloads
0

Comments
comments powered by Disqus

Cbox Relay client

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 a relay mail transport so MAIL_MAILER=relay works.
  • 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.webhook middleware 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.