wisedata/mail-php
wisedata/mail-php is a PHP mail package for Laravel and general PHP applications, offering a simple API for composing and sending email. It aims to make mail configuration and integration straightforward for projects that need dependable message delivery, with a clean developer experience.
This is a framework-agnostic PHP SDK for the WiseData Mail HTTP API (contacts + transactional email), not a Laravel package. There is no service provider, no auto-discovery, no config file to publish. Treat it like adding Stripe or an AWS SDK: you wire it up yourself.
composer require wisedata/mail-php
Look here first, in this order:
README.md — likely short and possibly in Portuguese; skim for the constructor signature.src/ — find the main client class and its constructor. You need to know: does it take an API key, a base URL, and an optional PSR-18/Guzzle client?composer.json — confirm the PHP version constraint and whether Guzzle is a hard dependency. This determines your testing strategy.Pull credentials from the WiseData dashboard and put them in config/services.php, not in a vendor config file:
// config/services.php
'wisedata_mail' => [
'key' => env('WISEDATA_MAIL_KEY'),
'url' => env('WISEDATA_MAIL_URL', 'https://api.wisedata.com.br'),
],
AppServiceProvider::register():$this->app->singleton(\WiseData\Mail\Client::class, fn () => new \WiseData\Mail\Client(
config('services.wisedata_mail.key'),
config('services.wisedata_mail.url'),
));
php artisan tinker — not from a controller, not from a queued job. One call, one email in your inbox, then stop and read the response object shape before building anything else.Wrap it, don't sprinkle it. The vendor namespace should appear in exactly one or two files. Create App\Services\Mail\WiseDataMailer that exposes intent-revealing methods (sendReceipt, syncContact) and returns your own small result object. This is your insurance policy against the package being abandoned — swapping to raw Http:: calls later becomes a two-hour job instead of a refactor.
Transactional sends belong in jobs. The SDK has no queue awareness, no retry policy, and no backoff. Put every send inside a queued job with $tries, $backoff, and failed() handling. If the API supports an idempotency key, pass a deterministic one (e.g. order-{id}-receipt) so retries don't double-send; if it doesn't, dedupe with a sent_at column before dispatching.
Contact sync via model events, not controllers. Hook User::created / updated → SyncContactToWiseData job. Use updated sparingly — a naive listener will fire an API call on every last_seen_at touch. Guard with $user->wasChanged(['name','email','marketing_opt_in']).
Backfill with a chunked Artisan command. The contacts API will rate-limit you on a 10k-user import. User::chunkById(200, ...) with a usleep() or RateLimited job middleware beats a single loop that dies at record 400.
Dual-driver is normal. Most teams keep Laravel's native Mail for ops/internal mail (password resets, error alerts) and use WiseData only for product transactional mail where deliverability analytics matter. Don't try to force the SDK into a custom Mail transport unless you genuinely need Mail::to()->send() ergonomics — it's a lot of work for little gain.
Testing: bind a fake into the container in TestCase::setUp() and assert on your wrapper's calls, not the vendor client. If you need to test the client itself, check whether it accepts an injected Guzzle client — if it does, use MockHandler. If it constructs new GuzzleHttp\Client() internally, you can't intercept it and you should test only at the wrapper boundary.
Mail:: integration, no queue hooks. The package gives you an HTTP client and nothing more. That's fine — just don't assume Laravel conventions exist.src/. With zero stars and zero dependents there's no Stack Overflow corpus. The README is the only doc; if method signatures don't match it, trust the code.catch (\Exception $e) around a send will silently swallow a malformed-API-key error that should have paged you.['timeout' => 10, 'connect_timeout' => 5].laravel.log is a LGPD/GDPR problem. Log the contact ID and status code only.^1.0 bump is entirely yours. Check that the tag actually resolves on Packagist before assuming the advertised release exists.json_decode($body, true) and hand back arrays, which means no IDE completion on the response. Wrap the response in a typed DTO in your service class so the rest of your app isn't doing $response['data']['id'] ?? null guesswork.How can I help you explore Laravel packages today?