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

Mailjet Apiv3 Php Laravel Package

mailjet/mailjet-apiv3-php

Official PHP wrapper for the Mailjet API v3. Send transactional email and SMS, manage contacts and campaigns through a simple, PSR-18 compatible client. Requires PHP 8.1+ and installs easily via Composer.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • This is not a Laravel package — it is a framework-agnostic PHP SDK (mailjet/mailjet-apiv3-php) with no ServiceProvider, no facade, no publishable config, and no Laravel version constraints. Treat it as a vendor transport library, not a drop-in Laravel integration. Anything that looks like "Laravel integration" (config binding, credential resolution, queueing, event dispatch) is code we own.
  • The SDK is a thin, transparent HTTP wrapper over Mailjet's REST API: Client + Resources constants + get/post/put/delete returning a Response value object (success(), getStatus(), getData(), getReasonPhrase()). There is no domain model, no repository abstraction, no DTO layer. That is actually favorable for architecture: the surface area is small enough to hide behind our own port interface without fighting opinionated internals.
  • Modern architecture signals are good: PHP 8.1+, PSR-18 HTTP client and PSR-17 factories with php-http/discovery auto-wiring. PSR-18 injection is the single most valuable property here, because it lets us decorate the transport (retry, logging, circuit breaking, metrics, test doubles) without patching vendor code.
  • Weak fit for anything beyond message/contact operations: no webhook handling, no event ingestion, no template rendering, no idempotency, no rate-limit awareness, no async/queue model. Those are ours to build.

Integration feasibility

  • Installation is trivial (composer require mailjet/mailjet-apiv3-php); Composer auto-installs a compatible PSR-18 client if none exists. In a Laravel app we already have Guzzle, so the discovery path is a no-op.
  • Realistic integration scope for a TPM estimate: 1–2 engineer-days for a container binding + config + a MailjetTransport adapter + tests; +3–5 days if the scope includes contact/list sync, webhook ingestion, and per-message error reconciliation.
  • Credentials are constructor-injected (public key + secret). No credential provider, no token refresh, no multi-tenant abstraction — a single-tenant singleton binding is the naive path and will be wrong for any multi-brand/multi-tenant product. Plan a factory keyed by tenant/brand from day one.
  • Testing story is acceptable but not free: the third constructor argument ($CALL = false) only suppresses outbound calls; it does not produce a realistic fixture response. Proper testing requires injecting a mock PSR-18 client (or Laravel's Http::fake() layered under Guzzle). Budget for building an internal fake rather than relying on $CALL=false.

Technical risk

  • Silent partial failure (highest severity). Mailjet Send API v3.1 can return HTTP 200 with per-message errors inside the response body. success() reflects transport/HTTP success, not delivery acceptance. Any code that gates on success() alone will drop mail without alerting. This is a correctness bug waiting to happen and must be addressed in the adapter, not left to feature teams.
  • Future-dated release metadata. "Last release: 2026-07-31" is inconsistent with the README's version 2.0.0 badge and cannot be true as of assessment. Either the registry data is wrong or the artifact is not what it claims. Verify the actual tagged release and its provenance before approving, and pin an explicit version rather than floating.
  • Low ecosystem gravity. 274 stars, 0 dependents, score 28.77, Travis CI badge (long-deprecated CI). This is not a signal of abandonment — it is the official vendor wrapper — but it does mean few Stack Overflow answers, few third-party battle reports, and no community-maintained Laravel bridge to lean on. Our own engineers are the support tier.
  • Vendor concentration. Mailjet is Sinch-owned and the wrapper is officially maintained, which reduces (but does not eliminate) abandonment risk. The real lock-in is not the SDK — it's the account, sending domain reputation, template inventory, and contact lists. The SDK itself is swappable in a day because it's thin.
  • Data residency / GDPR. Endpoint selection is manual via options['url'] (api.mailjet.com vs api.us.mailjet.com). A misconfiguration routes PII to the wrong jurisdiction with no runtime guard. This needs to be a validated config value, not a free-form string.
  • No idempotency keys, no built-in retry/backoff. Retries on timeout risk duplicate sends. Mailjet does not expose an idempotency mechanism here, so duplicate-send suppression must be handled at our layer (dedupe keys, outbox pattern) or accepted as a documented risk.
  • Documented transport flakiness. The README explicitly calls out intermittent SSL_ERROR_SYSCALL and prescribes forcing IPv4. That is a known operational tax, not an edge case.

Key questions

  1. What is the actual current tagged version and release date, and does the 2.0.0 major bump (PSR-18 migration) represent a breaking change from any version we or a predecessor team already run?
  2. Do we need only transactional send, or also contacts/lists/templates/stats/SMS? If it's send-only, symfony/mailjet-mailer via Laravel's Mail manager may be the cheaper path and this SDK unnecessary.
  3. Which Mailjet region is our account on, and does that match our data-residency commitments?
  4. What is our sending volume and peak burst, and does Mailjet's contract cover it? (SDK choice is irrelevant if rate limits are the constraint.)
  5. Do we require delivery-event webhooks (bounce, spam, unsubscribe) for compliance or deliverability? If yes, that is unscoped work this library does not provide.
  6. Is there an existing ESP integration (SES/Mailgun/Postmark/SMTP) whose abstraction we should conform to rather than inventing another?
  7. Who owns sending-domain authentication (SPF/DKIM/DMARC) and reputation monitoring — this is the actual failure surface, not the PHP code.

Integration Approach

Stack fit

  • Laravel + PHP 8.1 or later is required; applications still on PHP 8.0 or 7.x cannot adopt without an upgrade. Confirm the runtime baseline before committing — this is the most common silent blocker.
  • Guzzle is already present in nearly every Laravel install, so PSR-18/PSR-17 discovery resolves without adding dependencies. If the app uses Symfony HttpClient, inject Symfony\Component\HttpClient\Psr18Client explicitly plus nyholm/psr7 rather than relying on discovery.
  • Bind the client as a singleton per credential set in a dedicated service provider, configured from config/services.php (consistent with Laravel's existing Mailgun/Postmark/SES conventions) rather than env() calls scattered at call sites.
// config/services.php
'mailjet' => [
    'key'      => env('MAILJET_APIKEY_PUBLIC'),
    'secret'   => env('MAILJET_APIKEY_PRIVATE'),
    'version'  => 'v3.1',
    'url'      => env('MAILJET_API_URL', 'api.mailjet.com'),
    'timeout'  => 10,
],

// AppServiceProvider::register()
$this->app->singleton(MailjetClientFactory::class, fn ($app) => new MailjetClientFactory(
    $app['config']->get('services.mailjet'),
    $app->make(\GuzzleHttp\ClientInterface::class),
));
  • Do not inject \Mailjet\Client directly into application code. Define our own narrow port (TransactionalMailer::send(Message $m): SendResult) and keep the SDK behind it. Given 274 stars and 0 dependents, the SDK is a dependency we must be able to replace in a day; a leaked vendor type turns a one-day swap into a two-week refactor.
  • Injected Guzzle handlers become the natural home for retry with jittered backoff on 429/5xx, request/response logging with credential redaction, timeouts, and metrics. This is the main reason to prefer this SDK over hand-rolled cURL.

Migration path

  • From SMTP or Laravel's default mailer: lowest-risk path — swap the mail transport only, keep queueing and templating as-is. Recommended first step.
  • From a competing ESP SDK (Mailgun/Postmark/SES): migrate behind the port interface. Run both providers in parallel via a feature flag on a per-message-class basis (e.g. transactional first, then marketing), comparing delivery webhooks for a defined soak window before cutover.
  • From an existing Mailjet integration (older wrapper version, or a Symfony MailjetBundle): the v2.0.0 PSR-18 migration changed the constructor and removed bundled HTTP coupling — audit for direct curl/Requests usage in legacy code, and check whether the old client set the v3.1 option (a silent default of v3 produces different payload semantics).
  • Abstraction-first sequencing matters more than the SDK swap itself. If the port interface lands before the provider swap, rollback is a config change; without it, rollback is a release.

Compatibility

  • version option is per-client, not per-call: v3 (email, default), v3.1 (send), v4 (SMS/contacts). A single misconfigured client silently uses v3 for sends — validate the version string at boot and fail fast.
  • PUT behaves as PATCH (partial update semantics). Any code ported from a provider with strict full-replace PUT will produce surprising partial-state bugs.
  • DELETE returns 204 with an empty body — do not assume a payload. SMS/v4 resources use ID where v3 uses id; casing inconsistency across resources is a real source of defects.
  • PHP version floor is 8.1; no Laravel-version constraint exists, so there is no Composer guardrail telling you the integration is incompatible — you discover it at runtime.

Sequencing

  1. Verify the actual released version, changelog, and license/provenance; pin it explicitly in composer.json.
  2. Confirm PHP baseline, Mailjet account/region, and sending-domain authentication.
  3. Land the port interface + adapter + fake PSR-18 test double (no production send yet).
  4. Land config, container binding, redacted logging, and timeout/retry policy.
  5. Enable transactional send for one low-risk message class behind a flag; soak 2–4 weeks.
  6. Expand to all transactional classes; add per-message error reconciliation for v3.1 partial failures.
  7. Add webhook ingestion (bounce/complaint/unsubscribe) with the appropriate verification, plus suppression-list handling.
  8. Only then: contacts/lists/templates/stats, if in scope. Marketing/bulk sending is a separate workstream with its own compliance review.

Operational Impact

Maintenance

  • Vendor-owned, MIT-licensed, low-cadence. The wrapper is maintained by Mailjet itself, so upstream breakage is fixed by the party with the strongest incentive to fix it. The flip side of low cadence: PSR-18 support only arrived at v2.0.0, and the Travis CI badge suggests CI modernization has lagged. Expect infrequent but occasionally breaking releases; pin versions and review changelogs rather than floating.
  • 0 dependents / 274 stars means we will not find community answers. Budget for the integration owner to read Mailjet's API reference directly rather than search for solutions. Internal documentation of our adapter is not optional — it becomes the institutional memory.
  • Our maintenance burden is the adapter, not the SDK. Roughly 600–900 lines of glue (config, factory, adapter, result mapping, webhook controller, fakes) that we own forever, including the v3.1 partial-failure reconciliation logic. That is the real cost of "no Laravel package."

Support

  • Support is vendor support on the Mailjet account plus our own first-line triage — there is no community maintainer, no Laravel-specific issue channel, and no expectation of response on GitHub issues for integration problems.
  • The most common support ticket will be "email didn't arrive", which is almost never this SDK: it is domain reputation, DKIM misconfiguration, suppression lists, or the v3.1 partial-failure case. Make per-message response bodies retrievable in logs before launch, or every such ticket becomes an unbounded investigation.
  • The README's IPv4 workaround and the v3/v3.1 default are predictable, recurring "why is this behaving oddly" questions — put both in the runbook up front.

Scaling

  • Rate limits and reputation, not the SDK, are the scaling ceiling. Throughput comes from batching (multiple messages per request in v3.1) and from queue workers, not from the library.
  • Blocking synchronous HTTP in the request cycle is the primary anti-pattern. Any send in a web request adds 100–800ms of third-party latency and couples our availability to Mailjet's. All sends go through Laravel queues with a dedicated connection; the SDK call happens inside the job.
  • Per-tenant credential instances must be pooled/cached; constructing a Client per message is cheap but constructing a Guzzle client per message is not, and it defeats connection reuse.
  • Worker concurrency must respect provider rate limits. Add a rate limiter to the queue; Mailjet's 429s are handled by neither the SDK nor Laravel by default.

Failure modes

Failure Detection Blast radius Mitigation
v3.1 HTTP 200 with per-message errors Only by parsing response body Silent non-delivery Reconcile per-message status; alert on any error entry; never gate on success() alone
Timeout / SSL_ERROR_SYSCALL Exception or empty response Failed send, possible duplicate on retry Force-IPv4 Guzzle config; bounded retries; dedupe keys
429 rate limit Status code only Throttled queue Rate-limited queue worker; backoff on 429
version misconfiguration (v3 vs v3.1) Subtle wrong payload semantics Malformed/rejected sends Boot-time config validation
Wrong region endpoint Undetected PII to unintended jurisdiction Allow-listed config enum, not free string
Credentials in logs Undetected Security incident Redaction in Guzzle middleware; never log constructor args
Duplicate send on retry Recipient complaint Trust/reputation damage Outbox/dedupe pattern or accept-and-document
Vendor outage 5xx / connection errors No transactional mail Queued retry with backoff; secondary-provider failover only if explicitly built
  • No circuit breaker exists in the library. Without one, a Mailjet outage will saturate queue workers with retries and starve other jobs — a cross-domain failure that is easy to overlook in design review.

Ramp-up

  • Engineer ramp-up on the SDK is low (~half a day) — the API surface is four verbs, a Resources constant map, and a Response object. Comprehension cost is genuinely small.
  • Ramp-up on Mailjet's semantics is the real cost (~2–3 days to competent, 1–2 weeks to confident): three coexisting API versions with different conventions, PUT-as-PATCH, inconsistent id/ID casing, filter/pagination syntax, and the v3.1 partial-failure model. Document these internally; the README will not.
  • Operational ramp-up for on-call (~1 day) requires a runbook covering: how to query a message by ID, where response bodies are logged, how to read the suppression list, how to interpret webhook events, and how to fail over. Without this, the first delivery incident consumes the whole team.
  • Recommendation: adopt, but as a vendor SDK behind an owned port interface, on the queue, with partial-failure reconciliation and region validation built before the first production send. The library is small, official, and low-risk; the integration is where the TPM risk lives.
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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table