Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Resend Php Laravel Package

resend/resend-php

The official PHP client for the Resend email API. Install via Composer on PHP 8.1+, then send email in a few lines with a simple client interface inspired by OpenAI PHP.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

  • Pros:

    • Laravel-native compatibility: The package aligns with Laravel’s service container, dependency injection, and facade patterns. The provided Laravel example demonstrates seamless integration with Laravel’s config, services.php, and Mail facade.
    • Modular, resource-based design: The SDK follows a resource-based structure (emails, contacts, templates, automations, events, usage, and now inboxes), which maps cleanly to Laravel’s service-layer organization and keeps each capability independently testable and injectable.
    • API parity: Supports Resend’s full feature set (transactional emails, templates, webhooks, contacts, automations, events), reducing the need for custom HTTP clients.
    • New in v1.16.0 — usage service: A first-class resource for account-level usage and quota data (PR #151). Architecturally significant for a TPM because it closes the biggest observability gap in the prior assessment: quota and consumption are now retrievable through the same SDK client, so capacity signals can be surfaced inside the application (admin dashboards, health checks, alerting) rather than only in Resend’s external console.
    • New in v1.17.0 — inboxes resource (PR #153): The SDK’s first resource that is not an outbound action. Where every prior resource is "call it and something is sent/created," inboxes implies a managed email address or inbound surface, which introduces a receiving path into the application. Architecturally this is the most consequential release since the SDK baseline: it moves the integration from "outbound client behind a queue" to potentially "outbound client plus an ingress endpoint that must terminate, verify, persist, and route inbound mail." The resource pattern itself is consistent, so the container binding, facade abstraction, and test seams extend without redesign — the new work is all on the application side.
    • PHP 8.1+ compliance: Leverages modern PHP features (typed properties, enums) while maintaining a broad framework support window.
    • Idempotency and retries: Built-in support for Idempotency-Key and rate-limiting error handling aligns with Laravel’s queue/retry systems (e.g., Illuminate\Queue).
  • Cons:

    • inboxes scope is undocumented in the release: The changelog lists "Added inboxes endpoints" with no description, no docs link, and no example. For a TPM this is a blocking ambiguity rather than a minor gap: whether inboxes means (a) inbound email receiving, (b) provisioning of inbox/alias addresses, or (c) a management surface over sending identities, the integration design differs completely. Do not plan against inboxes until the shape of the endpoints is confirmed in a spike.
    • No Laravel-specific abstractions: Unlike laravel-notification-center or spatie/laravel-mailables, this SDK lacks built-in integration with Laravel’s Notifiable trait or Mail classes. A TPM would need to bridge this gap (e.g., via a custom facade or service provider).
    • Event/webhook handling: While the SDK supports webhooks, Laravel’s event system (e.g., Illuminate\Bus\Dispatchable) isn’t natively integrated. A TPM would need to design a listener layer (e.g., ResendWebhookHandler) to translate Resend events into Laravel events.
    • usage is read-only and unopinionated: The new service returns account-level data but provides no scheduling, caching, or threshold alerting. Integrating it means owning a polling cadence and a cache strategy — an application-layer concern the TPM must design, not a capability the package delivers.
    • inboxes is even more unopinionated: If it is an inbound surface, the SDK will hand back messages; it will not provide storage, threading, deduplication, attachment handling, spam filtering, PII retention policy, or a reply-routing model. That is a meaningful new application tier, not a client call.
    • Growing surface area: The SDK has expanded from the v1.4.0 baseline to v1.17.0 (thirteen minor releases) covering automations, events, usage, and now inboxes. Each new resource is another integration point to validate against Laravel’s container, config, and queue patterns — and inboxes is the first that doesn’t inherit the existing outbound pattern for free.

Integration Feasibility

  • Laravel Ecosystem Fit:
    • Service Container: The SDK’s Resend::client() can be registered in config/services.php and bound to the container, enabling dependency injection in controllers/services.
    • Mail Facade: Can be extended to work alongside Laravel’s Mail facade (e.g., via a custom ResendMailer class).
    • Queue Integration: Supports Laravel’s queue system for async email sending (e.g., Resend::emails->send() wrapped in a job).
    • Scheduler Integration: usage fits naturally into Laravel’s task scheduler — a scheduled command can poll usage/quota on a fixed cadence, cache the payload, and dispatch alerts on threshold breach. No new infrastructure is required.
    • Inbound Integration (new): If inboxes is a receiving surface, Laravel is a good host — a signed webhook route, a queue job to normalize the payload, Storage for raw MIME and attachments, an Eloquent model for persisted messages, and Illuminate\Notifications for routing the inbound message to a human. All of it is app-owned, none of it comes from the package.
  • Database/ORM: No direct Eloquent integration, but contacts/audiences can be mapped to Laravel models (e.g., User extending ResendContact). Usage snapshots can be persisted to a lightweight table if historical trending is required, but caching alone is sufficient for most teams. Inbound messages almost certainly need persistence — a resend_inbound_messages table (id, inbox, from, subject, raw reference, received_at, processed_at) plus a dedupe key, which contradicts the prior "no schema changes" position for that scope only.
  • Testing: The SDK includes PHPUnit tests, but Laravel-specific tests (e.g., for queue jobs, facades, scheduled usage polls, or inbound payload handling) would need to be written. An inbound path additionally needs a replay test suite with captured payloads and a duplicate-delivery assertion.

Technical Risk

  • Medium, with a new opt-in high-risk pocket:
    • API Versioning: Resend’s API evolves rapidly. The package retains a healthy cadence (v1.4.0 → v1.17.0 with no breaking changes declared in either of the last two releases), but a TPM should treat minor-version drift as a standing review item and re-validate the framework support matrix, which may have moved beyond the Laravel 8.x–10.x window documented at assessment time.
    • Error Handling: Custom error classes (e.g., ResendException) may need wrapping to align with Laravel’s exception hierarchy (e.g., Illuminate\Mail\MailerException).
    • Performance: Batch operations (e.g., contacts->upsert) could impact Laravel’s request lifecycle. A TPM should benchmark under load.
    • Quota polling cost: usage calls are outbound HTTP requests. Naive per-request polling would add latency and burn rate limit. This must be cached and scheduled, not called inline in the request path.
    • New: inbound data-ingress risk (inboxes): Inbound email is untrusted input. HTML bodies, attachments, and headers arrive from arbitrary senders. Persisting raw payloads without verification, size limits, or sanitization is a security and storage-cost exposure. Signature/source verification is mandatory, not optional.
    • New: inbound is at-most-once from our side: Outbound failures fall into a queue we control and can retry. Inbound delivery depends on the vendor retrying and on our endpoint returning success. A 500 from a misconfigured route can silently drop mail with no compensating action on our side.
    • New: redelivery and duplication: Inbound events are commonly retried; without an idempotency key on persistence, one email becomes two tickets or two notifications.
    • Mitigation:
      • Use Laravel’s config/cache.php to store Resend API keys/endpoints, and to cache usage responses with a TTL aligned to the polling cadence.
      • Implement a ResendServiceProvider to centralize configuration and bindings.
      • Add a ResendFacade to abstract SDK calls (e.g., Resend::sendWelcomeEmail(), Resend::quotaSnapshot()).
      • For inboxes: spike the endpoints in isolation first, verify signatures, cap payload/attachment size, store to a dedicated disk with a retention policy, and dedupe on the vendor message identifier before writing.

Key Questions

  1. Email Workflow Strategy:
    • Will this replace Laravel’s Mail facade entirely, or coexist with it? If the latter, how will conflicts (e.g., from address validation) be resolved?
  2. Webhook Routing:
    • How will Resend webhooks be routed to Laravel’s event system? Will a ResendWebhookController be created, or will events be dispatched via a service?
  3. Template Management:
    • Will Resend templates replace Laravel’s Blade/Markdown emails, or supplement them? If hybrid, how will template IDs be mapped?
  4. Contact Sync:
    • How will Laravel models (e.g., User) sync with Resend contacts? Will a ResendContactable trait be created?
  5. Monitoring:
    • With the usage service available, is in-app quota telemetry now in scope? What is the polling cadence, cache TTL, and alert threshold? Where does quota data surface — admin dashboard, /health endpoint, or log channel only?
  6. Inbox Scope (new, blocking):
    • What does inboxes actually expose — inbound receiving, address provisioning, or sending-identity management? Is there a product need (support@, replies-to-transactional, inbound parsing) that justifies adopting it now, or is this a "track, don't adopt" item? If inbound: which address(es), which routing destination (ticket system, notification, mailbox), and who owns the reply path?
  7. Inbound Data Handling (new, conditional):
    • What is the retention policy for inbound bodies and attachments, what PII classification applies, where is raw MIME stored, and what is the maximum accepted message size?
  8. Fallback Strategy:
    • If Resend’s API fails — or quota is exhausted, which usage now lets us detect before failure — will emails fall back to Laravel’s default mailer (e.g., log, ses)? If so, how will this be implemented? Note there is no equivalent fallback for inbound: an alternative ingress is a different vendor decision.
  9. Version Drift:
    • The package moved from v1.4.0 to v1.17.0 since the last assessment. Does the current framework support matrix still cover our target Laravel version, and who owns re-validation on each minor release?

Integration Approach

Stack Fit

  • Laravel Core:
    • Service Container: Register the SDK as a singleton in AppServiceProvider:
      $this->app->singleton(Resend::class, fn() => Resend::client(config('services.resend.key')));
      
    • Configuration: Add resend to config/services.php:
      'resend' => [
          'key' => env('RESEND_API_KEY'),
          'webhook_secret' => env('RESEND_WEBHOOK_SECRET'),
          'usage_cache_ttl' => env('RESEND_USAGE_CACHE_TTL', 300),
          'quota_alert_threshold' => env('RESEND_QUOTA_ALERT_THRESHOLD', 0.8),
          'inbox_id' => env('RESEND_INBOX_ID'),
          'inbound_disk' => env('RESEND_INBOUND_DISK', 'local'),
          'inbound_max_bytes' => env('RESEND_INBOUND_MAX_BYTES', 26214400),
      ],
      
  • Mail System:
    • Option 1: Extend Laravel’s Mailer to use the SDK:
      class ResendMailer extends Mailer {
          public function send(MailableContract $mailable, array $failures = []) {
              $resend = app(Resend::class);
              $resend->emails->send($this->prepare($mailable));
          }
      }
      
    • Option 2: Create a custom ResendMailer facade for SDK-specific calls.
  • Queue System:
    • Wrap SDK calls in Laravel jobs (e.g., SendResendEmailJob) for async processing:
      class SendResendEmailJob implements ShouldQueue {
          use Dispatchable, InteractsWithQueue;
      
          public function handle() {
              Resend::emails()->send($this->emailData);
          }
      }
      
  • Usage / Quota Telemetry:
    • Poll on a schedule inside a job, cache the result, and alert on threshold breach:
      class PollResendUsageJob implements ShouldQueue {
          use Dispatchable, InteractsWithQueue;
      
          public function handle() {
              $usage = Cache::remember(
                  'resend.usage',
                  config('services.resend.usage_cache_ttl'),
                  fn () => Resend::usage()->get()
              );
      
              if ($this->ratio($usage) >= config('services.resend.quota_alert_threshold')) {
                  // Notify ops channel; optionally degrade to fallback mailer
              }
          }
      }
      
  • Inboxes / Inbound (new, opt-in):
    • Terminate on a signed route, acknowledge fast, process off-request:
      Route::post('/resend/inbound', [ResendInboundController::class, 'handle'])
          ->withoutMiddleware(['csrf']);
      
      public function handle(Request $request) {
          abort_unless(
              Resend::verifyWebhook($request->getContent(), $request->header('x-resend-signature'), config('services.resend.webhook_secret')),
              401
          );
      
          ProcessResendInboundMessage::dispatch($request->all());
      
          return response()->noContent();
      }
      
    • Persist with a dedupe key so vendor retries don’t double-process:
      ResendInboundMessage::firstOrCreate(
          ['provider_message_id' => $payload['id']],
          ['inbox_id' => config('services.resend.inbox_id'), 'from' => $payload['from'], 'subject' => $payload['subject'], 'received_at' => now()]
      );
      
    • Store raw MIME/attachments on a dedicated disk with a retention job; route the message onward via Notification to the owning team.
  • Webhooks:
    • Route /resend/webhook to a controller with signature verification:
      public function handleWebhook(Request $request) {
          $payload = $request->getContent();
          $signature = $request->header('x-resend-signature');
      
          if (!Resend::verifyWebhook($payload, $signature, config('services.resend.webhook_secret'))) {
              abort(401);
          }
      
          event(new ResendWebhookReceived($payload));
      }
      

Migration Path

  1. Phase 1: SDK Integration
    • Install the package: composer require resend/resend-php.
    • Register the SDK in AppServiceProvider.
    • Replace direct HTTP calls to Resend with SDK methods (e.g., Resend::emails()->send()).
  2. Phase 2: Laravel Facade/Service Layer
    • Create a ResendFacade or ResendService to abstract SDK calls.
    • Example:
      // app/Services/ResendService.php
      class ResendService {
          public function sendWelcomeEmail(User $user) {
              return Resend::emails()->send([
                  'from' => '[email protected]',
                  'to' => $user->email,
                  'subject' => 'Welcome!',
                  'html' => view('emails.welcome', ['user' => $user]),
              ]);
          }
      }
      
  3. Phase 3: Webhook & Event Integration
    • Add webhook endpoint and event listeners.
    • Example listener:
      // app/Listeners/HandleResendEvent.php
      class HandleResendEvent {
          public function handle(ResendWebhookReceived $event) {
              $data = $event->payload;
              if ($data['event'] === 'email.bounce') {
                  // Update user's email status in DB
              }
          }
      }
      
  4. Phase 4: Contact & Template Sync
    • Sync Laravel models with Resend contacts (e.g., via model observers or jobs).
    • Example observer:
      // app/Observers/UserObserver.php
      class UserObserver {
          public function saved(User $user) {
              Resend::contacts()->upsert($user->email, [
                  'name' => $user->name,
                  'metadata' => ['user_id' => $user->id],
              ]);
          }
      }
      
  5. Phase 5: Usage & Quota Telemetry
    • Schedule PollResendUsageJob (e.g., every 5–15 minutes) in app/Console/Kernel.php or via Schedule::job().
    • Choose a surface for the data: admin dashboard widget, /health endpoint extension, or a log channel consumed by an existing alerting stack.
    • Wire the threshold check into the fallback path established in Phase 1 so quota pressure degrades gracefully rather than producing hard send failures.
  6. Phase 6: Inboxes (new, conditional and opt-in)
    • Gate on a scope decision, not on engineering capacity. First run a 1–2 day spike against the inboxes endpoints in a sandbox to establish what they return and what lifecycle they imply.
    • If inbound is in scope: stand up the signed route, acknowledgement path, dedupe-backed persistence, storage disk with retention, and onward routing. Roll out against a single low-traffic address before any customer-facing one.
    • If it is provisioning/identity management only: fold it into the existing ResendService abstraction and treat it as a Phase 1-level capability with no new infrastructure.
    • Sequence last regardless: it is additive, does not touch outbound sends, and depends on the verification, queue, and storage patterns established earlier.

Compatibility

  • Laravel Versions: Documented as PHP 8.1+ during the v1.4.0 assessment (Laravel 8.x–10.x). Given thirteen subsequent minor releases, re-validate the current support matrix against your target LTS before committing.
  • Dependencies: No conflicts with Laravel’s core packages (e.g., illuminate/mail, guzzlehttp/guzzle). Neither usage nor inboxes introduces new transitive dependencies. The v1.17.0 dependency change is a CI action digest pin (shivammathur/setup-php) and is not shipped code — no runtime impact.
  • Database: No schema changes required for the outbound, webhook, contact, or usage scopes. Usage data can live in cache; an optional resend_usage_snapshots table only if historical trending is a product requirement. Inbound scope is the exception: it will require a messages table plus a storage disk and a retention job.
  • Third-Party Packages:
    • spatie/laravel-activitylog: Can log Resend events, quota threshold breaches, and inbound message handling.
    • spatie/laravel-medialibrary: Can integrate with Resend’s file attachments, and can be used to manage stored inbound attachments if that scope is adopted.

Sequencing

  1. Prerequisites: Confirm framework support matrix for the v1.17.x line, provision API key and webhook secret, and decide the mail strategy (replace vs. coexist with Mail facade). Resolve the inboxes scope question in parallel — it does not block Phases 1–5 but it does determine whether Phase 6 exists at all.
  2. Phase 1–2 (Foundation): Container binding, config entries, and ResendService abstraction. Ship behind a feature flag for one low-risk transactional email.
  3. Phase 3 (Event Loop): Webhook endpoint with signature verification and event translation. This is the highest-risk phase — gate it on a verified signature test suite.
  4. Phase 4 (Data Sync): Contact and template synchronization via observers/jobs. Roll out per-model to limit blast radius.
  5. Phase 5 (Telemetry): usage polling, caching, and threshold alerting — including wiring quota detection into the documented fallback path.
  6. Phase 6 (Inboxes, optional): Only after the spike confirms scope and a product owner claims the inbound use case. Independent of Phases 1–5 in risk terms; fully additive.

Operational Impact

Maintenance

  • Low-to-moderate for the SDK, unchanged by v1.17.0: The package remains a thin, resource-based client with no framework coupling, so maintenance is mostly version currency. The v1.4.0 → v1.17.0 delta (thirteen minors, no breaking changes declared) indicates an actively maintained package with predictable semver discipline. The Renovate digest pin confirms automated dependency hygiene in CI.
  • Ownership required: Someone must own the ResendServiceProvider, the ResendService abstraction, the usage polling schedule and cache TTL, and — if adopted — the entire inbound pipeline (route, dedupe, storage, retention job, routing destination). These are application artifacts, not package concerns, and will drift if unowned. The inbound pipeline in particular has an operational owner requirement that outbound never had: someone must be accountable for mail that arrives and isn't actioned.
  • Version watch: Establish a standing review of minor releases. Two releases in a row have added a new resource (usage, then inboxes), which is a fast enough surface-area expansion that "read the changelog" needs to be a named responsibility rather than an assumption.

Support

  • Vendor support: Resend maintains the SDK directly, and the Laravel example repo provides a canonical integration reference. PR-level attribution on releases (e.g., #151, #153) aids triage.
  • Documentation lag is now a live issue: v1.16.0's usage shipped with enough context to design against; v1.17.0's inboxes shipped as a one-line changelog entry. Until vendor docs catch up, the internal spike output is the source of truth and should be written down.
  • Internal support: The abstraction layer (ResendService, ResendFacade) is what the team will actually support day-to-day. Document the mail strategy decision, the quota alert runbook, and — if adopted — the inbound triage runbook (who watches the inbox, what "unprocessed message" means, escalation path) alongside it.
  • Escalation clarity: Because quota telemetry now lives in-app, the on-call path for "sends failing" is clearer — check the cached usage snapshot before escalating to the vendor. Inbound introduces a separate on-call question with no vendor-side equivalent: if mail stops arriving, is that Resend, our route, our queue, or our storage?

Scaling

  • Throughput: Email sending scales horizontally without changes, since the SDK is stateless and calls are queue-friendly. Batch contact operations remain the primary load concern and should be benchmarked.
  • Quota-aware scaling: The usage service lets scaling decisions be quota-informed rather than reactive. Teams can pre-emptively request quota increases, throttle non-critical sends, or route overflow to a secondary mailer before hitting hard limits.
  • Inbound scaling (new, if adopted): Inbound volume scales independently of outbound and is not under our control — it is driven by whoever emails the address. Two consequences: the ingress endpoint must shed load fast (acknowledge, queue, return), and storage grows monotonically with no natural ceiling. Budget a retention window and a pruning job from day one, not after the first disk alert.
  • Polling cost: Usage polling is cheap but non-zero. A 5–15 minute cadence with caching keeps it negligible; per-request polling would not scale and should be explicitly prohibited in the integration guidance.
  • Cache topology: On multi-node deployments, usage snapshots should live in a shared cache store (Redis/Memcached) rather than per-node file cache to avoid divergent alerting. Inbound storage should likewise be a shared/object disk, not node-local, or attachments become node-dependent.

Failure Modes

  • API unavailability / 5xx: Handled by Laravel queue retries with backoff. Requires the fallback mailer discussion to be resolved, otherwise sends silently queue-fill.
  • Rate limiting (429): The SDK surfaces rate-limit errors; queue retry semantics absorb transient bursts.
  • Quota exhaustion: Previously a silent cliff — sends would fail with no in-app warning. With usage, a threshold alert provides lead time. The residual risk is that polling lag (cache TTL) can still allow a burst to exhaust quota between polls; mitigate with a conservative threshold (e.g., 80%).
  • Webhook delivery failure or spoofing: Signature verification is mandatory; a missing or misconfigured secret is a silent security failure. Add a test that asserts a 401 on an invalid signature.
  • Usage endpoint permission errors: A key scoped without usage access would fail only on the polling job — a low-severity failure that should be caught in staging, but it will be invisible in production unless the job's failures are surfaced.
  • Polling job failure: Since this is app-owned code, a failing poll means alerts stop firing without anyone noticing. Add a dead-man's-switch or log-based alert on poll staleness.
  • Inbound message loss (new): The worst failure mode in the new surface, because it is silent in both directions. A 500 from our controller, an unverified signature due to a rotated secret, a full disk, or a queue that stops draining all result in mail the sender believes was delivered and we never action. There is no retry we control. Mitigate with: 2xx-on-accept-then-process, alerting on endpoint error rate, alerting on "inbound received but unprocessed > N minutes," and a documented manual reconciliation path with the vendor.
  • Inbound duplication (new): Vendor redelivery plus non-idempotent persistence produces duplicate tickets/notifications. The dedupe key is a correctness requirement, not an optimization.
  • Inbound content handling (new): Oversized attachments or malformed MIME can exhaust memory or disk if parsed inline. Enforce a byte cap at the route and parse in a queued job with a timeout.

Ramp-up

  • Low for email sending: A developer familiar with Laravel can wire the container binding and send a transactional email in under a day using the official example repo.
  • Moderate for the full outbound integration: Webhook translation, contact sync, and the usage/alerting layer each add a half-day to a day of design and testing, plus review time for the quota runbook.
  • Moderate-plus for inbound, if adopted: Add 2–3 days for the spike, the persistence and retention design, the dedupe test suite, and the triage runbook — plus a product decision that has to be made before any of it starts.
  • TPM-level onboarding: The assessment doubles as ramp documentation. The key decisions a new contributor must be briefed on are (1) replace vs. coexist with Mail, (2) fallback strategy, (3) the polling cadence and thresholds chosen for usage telemetry, and (4) whether inbound email is in scope and, if so, who owns it.
  • Recommendation: Treat v1.17.0 as a low-risk release with an ambiguous, potentially high-effort feature attached. The inboxes addition does not change any existing behaviour and carries no breaking changes or deprecations, so it should not perturb the current roadmap. Adopt it only in response to a named product need, and not before the spike resolves scope. Keep the standing recommendation intact: adopt usage at Phase 5, keep outbound phases unchanged, and let inboxes sit as a tracked, unevaluated capability until someone owns the use case.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator