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

Laravel Sendcloud Laravel Package

overtrue/laravel-sendcloud

A Laravel integration for the SendCloud email delivery service, providing a simple facade and configuration to send transactional and template emails through SendCloud's API. It wraps the SendCloud PHP SDK so you can fire off mail from any Laravel app with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Version 2.0.0 is a hard platform bump. Before anything else, confirm you are on PHP ^8.3 and Laravel ^13.30. Laravel 9–11 and every Laravel version below 13.30 are unsupported, so applications on those versions must stay on overtrue/laravel-sendcloud:^1.0 or upgrade the framework first.

composer require overtrue/laravel-sendcloud:^2.0

This pulls overtrue/sendcloud:^2.0 and requires Guzzle 7.15.2+. Check for a conflicting Guzzle pin in your composer.lock before you run the update; a locked-down Guzzle is the most common reason this install fails.

Where to look first, in order:

  1. config/services.php — the sendcloud block is still the source of truth (api_user, api_key, and any base/domain options your app sets). Fill it from env, e.g. SENDCLOUD_API_USER / SENDCLOUD_API_KEY.
  2. The registered facade alias — confirm the exact name from the package's composer.json → extra.laravel.aliases rather than guessing; discovery still registers the facade, the SDK binding, and the sendcloud alias.
  3. The overtrue/sendcloud 2.0 README for the endpoint methods themselves. This package is API bindings only — it is not a Laravel Mail transport. If your mental model is Mail::to(...)->send(), reset it now.

First use case — one transactional call through the container:

use Overtrue\SendCloud\SendCloud; // SDK client bound by the package

$client = app(SendCloud::class);

$result = $client->send([
    'from'    => '[email protected]',
    'to'      => '[email protected]',
    'subject' => 'Your receipt',
    'html'    => view('emails.receipt', $data)->render(),
]);

Wrap this in a small application-owned service class immediately. That single indirection is what makes the 2.0 facade-caching behaviour (below) a non-issue and keeps your call sites testable.

Implementation Patterns

Prefer constructor injection over the facade. Client construction is transient, but the facade caches its resolved client until Laravel resets facade state. Injection gives you a predictable per-resolution instance:

final class SendCloudMailer
{
    public function __construct(private readonly SendCloud $client) {}

    public function sendReceipt(Order $order): void
    {
        $this->client->send([...]);
    }
}

Resolve inside the job, not in the constructor. Because runtime config changes never propagate to an already-constructed or cached client, queue workers that live for hours must resolve the client in handle():

public function handle(SendCloudMailer $mailer): void
{
    $mailer->sendReceipt($this->order);
}

Multi-tenant / per-account credentials. Since configured credentials take precedence over anything passed per call, you cannot override credentials on a single shared client. Build a dedicated client per tenant and bind it, or set config and resolve a fresh client in a scoped container binding:

app()->forgetInstance(SendCloud::class);
config()->set('services.sendcloud.api_user', $tenant->api_user);
config()->set('services.sendcloud.api_key', $tenant->api_key);

$client = app(SendCloud::class); // reads config from the container at resolve time

This only works because 2.0 reads configuration from the container when the client is resolved — the application/container isolation fix. Config set after resolution is silently ignored, so keep ordering strict.

Attachments and multipart. Use the SDK's upload() helper or pass multipart options. Do not hand-build a multipart body and post it — 2.0 explicitly steers you away from prebuilt multipart payloads, and hand-rolled bodies are the classic source of malformed-request 400s.

Testing. The package's own suite (13 Testbench tests / 36 assertions) mocks the transport; do the same. Swap the binding in your feature tests rather than hitting the API:

$this->mock(SendCloudMailer::class)
    ->shouldReceive('sendReceipt')
    ->once();

For integration tests, inject a Guzzle MockHandler so you assert on the request path and auth headers without live credentials — matching how upstream verifies request paths and error responses.

If you want Mail:: integration, you still have to write it. Add a custom transport that delegates to this SDK client; do not expect a mail.driver from this package in 2.x.

Gotchas and Tips

  • Runtime config changes are a no-op on existing clients. config()->set() after the facade or a singleton has resolved the client changes nothing. Either set config first, or App::forgetInstance() the SDK class and reset the facade before re-resolving.
  • Octane compatibility is not claimed. Treat the client as per-request. If you run Octane, avoid caching the client in a singleton, and be explicit about clearing cached facade state on worker reset rather than assuming it happens.
  • Redirects are off by default. Explicitly enabled redirects must remain on the original origin; cross-origin redirects and HTTPS→HTTP downgrades now throw. If you previously relied on an endpoint redirect (or a proxy rewriting URLs), 2.0 will fail where 1.x "worked." Verify your base URL is the final one, and that any custom Guzzle middleware does not introduce a redirect.
  • Custom middleware needs review. The upgrade notes call this out for a reason: middleware that injected auth, rewrote URIs, or followed redirects may now be redundant, wrong, or actively breaking same-origin enforcement. Audit before shipping.
  • Leading-slash paths and /apiv2/. The SDK preserves /apiv2/ for leading-slash paths. Do not manually prefix /apiv2/ yourself — you will end up with /apiv2//apiv2/....
  • HTTPS is the default now. Any config or middleware that hardcodes http:// should be removed; combined with the downgrade-redirect block, plain-HTTP setups fail hard rather than quietly.
  • Composer security posture is part of the contract. The Laravel 13.30 floor is the lowest secure baseline verified for this release. Keep advisory blocking enabled (composer config audit.block-insecure true or equivalent in CI) and run composer audit in pipelines — otherwise the platform guarantee is nominal.
  • Mocked tests are not a smoke test. Upstream verifies with a mocked transport and no real credentials. Before you cut over, do one live send per environment (including one with an attachment) to catch credential/domain/from-address problems that mocks cannot see.
  • Upgrade guide first, not last: UPGRADE.md in the 2.0.0 tag covers the SDK 2.0 migration; the CI run link confirms the verified matrix (PHP 8.3/8.4/8.5 latest, plus 8.3 lowest-secure) — mirror that matrix in your own CI if this package is on a critical path.

Extension points worth knowing: the container binding for the SDK client (decorate or swap it for a configured subclass), and the package singleton for the facade behaviour. Both are the intended seams — reach for them rather than monkey-patching the facade or vendoring the SDK.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route