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

Sendfox Php Laravel Package

dyaskur/sendfox-php

A simple PHP client for the SendFox API, giving Laravel and plain PHP apps an easy way to manage contacts, lists, campaigns, and automations with a clean, framework-friendly wrapper.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Scope of this review: This supersedes the v0.1.0 baseline. v0.3.0 (reached via v0.2.0) adds four capabilities that materially change the risk profile: signed webhooks with replay suppression, bounded automatic retries, Idempotency-Key propagation in the mail transport, and cursor-style pagination on the API client. Three of the ten open questions from the baseline (error mapping, internal 429 handling, delivery feedback) are now answered in the code's favour; one new inbound attack surface and one new operational dependency have been introduced.

Architecture fit

  • Unchanged core: a Symfony Mailer TransportInterface plus a Laravel service provider registering an auto-discovered sendfox mailer. For apps already sending through Mail, Notifiable, or Mailable, integration remains configuration-level.
  • The package now has an inbound component. The service provider registers POST /sendfox/webhook itself and dispatches WebhookReceived. This is qualitatively different from a transport: it introduces a public, unauthenticated-at-the-router HTTP endpoint into our application, plus a persistent dedupe store. It belongs in the architecture review, not just the mail config review.
  • Verification is implemented well on paper: HMAC over the raw body against X-SendFox-Signature, plus a ±5-minute timestamp window and per-event-ID suppression of provider retries. That is the correct shape. The residual questions are where the dedupe state lives and whether it is safe under multiple app instances.
  • The API client surface (new SendFox($key, maxRetries: 5), paginate()) widens the package's remit from "mail transport" to "SendFox API client." That is useful — contact/audience sync is now plausible without a second SDK — but it also means the package becomes a dependency of data-sync jobs, not only of mail.
  • Still absent and still out of scope: provider-side templating, campaign scheduling, inbound email parsing, and any documented CID/inline-image support.

Integration feasibility

  • Outbound integration effort is unchanged: low. Mailer entry, credentials, driver selection.
  • Webhooks add real but bounded work: endpoint exposure decisions (route prefix, middleware, CSRF exemption, throttling), a durable event-ID store, event-type handling, and secret management/rotation. Estimate 2–4 days including tests, not hours.
  • Idempotency-Key is the single highest-value addition for us. It converts at-least-once queue semantics into effectively-once delivery for the provider, which was previously an unavoidable duplicate-send risk on any retry. Realising the benefit requires us to set a deterministic key per logical message (e.g. derived from the mailable class + a business key such as order ID), not a random UUID per attempt — a naive implementation gets the API call but not the guarantee.
  • Retry behaviour is now specified and sane: 429 with Retry-After is honoured, 5xx/network errors retried on idempotent methods, retry count tunable, retries disableable. This is better than most pre-1.0 packages.
  • New unknowns are concentrated in the webhook subsystem and in the interaction between package-level retries and Laravel queue-level retries.

Technical risk

  • Pre-1.0 versioning (still the highest risk). Three minor releases with no 1.0 and no stated deprecation policy. The constructor signature now documented as new SendFox($key, maxRetries: 5) is an example of the kind of surface that can shift. Pin exactly; keep the mailer name and any webhook path behind config.
  • Retry amplification. Package retries (up to 3) sit underneath Laravel queue retries. A 5xx burst can multiply API calls and, if we also configure queue tries, extend delivery latency unpredictably. Retry budget must be reasoned about end-to-end, not per layer.
  • Quota exhaustion is deliberately not retried. A 429 without Retry-After (monthly quota consumed) fails fast. Correct, but it means quota exhaustion manifests as a wave of hard failures in failed_jobs — it needs its own alert, not just generic send-failure alerting.
  • POST + 5xx is not retried. Because POST is non-idempotent by default, the client declines to retry it. Sensible, but it means a transient provider-side 5xx on a send fails the job even when we supplied an Idempotency-Key that would have made the retry safe. Whether the transport leverages the key it forwards is an open question and a candidate improvement.
  • Webhook endpoint is new attack surface. Unauthenticated (by the router) inbound POST that triggers application events. HMAC verification mitigates forgery, but we own: TLS termination, request body size limits, per-IP throttling, and the fact that verification requires the raw body (any middleware that re-encodes JSON will break signatures in a way that is hard to debug).
  • Dedupe store is now a correctness dependency. "Retries of the same event ID are skipped" implies state. If that state is a cache and the cache is flushed, shared-nothing across instances is required, or the cache is unavailable, behaviour degrades to either duplicate event handling or dropped events. Neither is acceptable for bounced handling. Must be confirmed as Redis/DB-backed and shared.
  • 5-minute replay window is tight enough to be reasonable but means clock skew between SendFox and our hosts is now a failure mode; NTP discipline matters.
  • Event taxonomy is unspecified. bounced appears in the example. Whether delivered/opened/complaint/unsubscribed exist, and whether bounce events distinguish hard vs soft, determines whether this genuinely replaces a bounce-monitoring path or only gestures at one.
  • Vendor dependency is unchanged. SendFox is still primarily a newsletter/marketing platform; transactional eligibility, rate limits, and attachment ceilings remain the gating commercial questions, not code questions.
  • Maturity signals remain thin. No test coverage, CI status, or support policy in the release notes. Fast minor cadence is a positive signal for activity and a negative signal for stability, simultaneously.
  • Pagination is low-risk and useful; the main caution is memory — a generator that "fetches each page only when needed" is good, but callers who iterator_to_array() it will still blow up on large contact lists.

Key questions Resolved since baseline: error/exception mapping (retries now explicit per status class), internal 429 handling (yes, when Retry-After present), delivery feedback (partially — webhooks).

  1. Supported matrix still unconfirmed: Laravel versions, PHP floor, Symfony Mailer ^6 vs ^7? Does the webhook route/provider work on our exact Laravel version?
  2. License, maintainer backing, test coverage/CI, security disclosure process — still unanswered, and now more important given an inbound endpoint.
  3. Where is the webhook event-ID dedupe store? Cache, database, filesystem? Is it safe across multiple app instances and across deploys?
  4. Is the webhook path configurable (/sendfox/webhook is a fixed literal in the notes)? Can it be prefixed or disabled per environment (e.g. off in local/CI)?
  5. How does the registered route interact with our middleware stack — CSRF exemption, auth, throttling, body-size limits? Does the package publish or let us override the route definition?
  6. Does the mail transport retry a POST when an Idempotency-Key is present? If not, is that on the roadmap? A transient 5xx on a send is the most common failure and currently fails the job outright.
  7. Is Idempotency-Key passed through verbatim, or transformed/normalised? What is the accepted length/charset, and does it dedupe against a TTL we should know about?
  8. Are retries applied to webhook responses we return, or only to outbound API calls? Do we need to respond 2xx quickly and process asynchronously to avoid provider retry storms?
  9. Full event-type enumeration: delivered, bounced (hard/soft), complaint, unsubscribe, opened? Does each carry a stable event ID and timestamp?
  10. Does WebhookReceived expose the raw payload plus typed accessors, and is the payload persisted anywhere by default (data-retention implications for recipient PII)?
  11. paginate() limits: max per_page, total-item behaviour on very large lists, and whether it is a generator (memory-safe) or an array.
  12. Multi-account: single services.sendfox.webhook_secret implies one account. How do we run two SendFox accounts (multi-brand) — separate secrets, separate routes?
  13. SendFox's documented transactional rate limits, Retry-After semantics, per-message attachment limits, and whether quota is shared with campaign sending.
  14. Does the transport still avoid real API calls under Mail::fake(), and does the webhook route stay inert in tests?

Integration Approach

Stack fit

  • Outbound: unchanged clean fit for Laravel 9+ apps using Mail. Neutral for non-Laravel Symfony. Poor fit where provider-side templating or scheduling is required.
  • Inbound: the webhook endpoint fits any Laravel app that can expose a route, but it presumes a shared cache/DB and a queue for async processing. Apps running multiple instances without a shared store should not enable it until question 3 is answered.
  • Pagination makes the package a reasonable fit for contact/audience sync jobs — a new use case worth evaluating separately rather than bundling into the mail decision.

Migration path

  • Same core safeguard: register sendfox as a non-default mailer; keep the incumbent configured and credentialed; keep the mailer name in config, not in code.
  • Add the webhook endpoint only after outbound is proven. Sequence it as its own change: route exposure, dedupe store, event handler, tests. Do not ship it in the same deploy as the transport switch — a failure in one would confuse diagnosis of the other.
  • Introduce a TransactionalMailer resolver (or mail.transactional.driver) so provider is switchable per environment and per mail type. With webhooks in play, the resolver must also gate event handling per provider, otherwise a driver switch leaves an orphan listener.
  • Adopt Idempotency-Key from day one of the PoC, using a deterministic business key. Retrofitting this later means auditing every send site.
  • Set explicit retry budget: choose either package retries (keep 3) or queue retries as the primary mechanism and tune the other down. Do not leave both at defaults.
  • Add a kill switch for webhooks (SENDFOX_WEBHOOKS_ENABLED) and for package retries (maxRetries: 0) so we can degrade without a code change.

Compatibility

  • Re-verify composer.json constraints — three minors in, the Symfony Mailer constraint is the most likely breakage point, and the webhook route registration is a new surface that version skews can break silently.
  • Check route collisions: a pre-existing /sendfox/webhook route, a catch-all, or a route-cache (route:cache) interplay. Auto-registered routes plus cached routes is a classic production-only surprise.
  • Confirm VerifyCsrfToken exemption and that no global middleware (body-parsing, JSON re-encoding, request logging that consumes the stream) sits between the router and the signature check.
  • Verify the test environment: Mail::fake() bypass intact, webhook route not calling out, signature verification unit-testable with a fixed secret.

Sequencing

  1. Due diligence (2–3 days): resolve questions 1–6 and 9 above; read the source for dedupe implementation, exception mapping, and whether POST retry consults the idempotency key.
  2. Outbound PoC in staging (2–3 days): full message-shape matrix — HTML+text, cc/bcc, reply-to, attachment, tags, custom headers — with a deterministic Idempotency-Key, then deliberately retry the job and confirm single delivery.
  3. Failure-mode testing (1–2 days): bad/revoked token, oversize attachment, malformed address, forced 429 with and without Retry-After, forced 5xx on send, network timeout. Confirm failed_jobs observability and that quota exhaustion is distinguishable from transient failure.
  4. Webhook hardening (2–4 days): verify HMAC against a captured real payload, confirm dedupe across two app instances, test replay inside and outside the 5-minute window, test event-ID reuse after a cache flush, add throttling and body-size limits, add a runbook for secret rotation.
  5. Canary in production (1–2 weeks): one low-risk outbound flow, both drivers live, webhooks enabled in observe-only mode (log WebhookReceived, take no action) to validate volume, event types, and dedupe behaviour against real traffic.
  6. Expand or exit: widen to core transactional flows and start acting on bounce events only if canary metrics are clean and a decision is made on 1.0 timing or a vendor support commitment.

Operational Impact

Maintenance

  • Pin the exact version. The v0.1 → v0.3 cadence confirms the package is moving quickly; assume minor bumps can break config keys, constructor signatures, or the webhook payload shape. Review changelogs before every bump.
  • Two dependencies to track (package, SendFox API) becomes three concerns: outbound transport, inbound webhook contract, and API client for sync jobs. Each can regress independently.
  • Webhook secret rotation is now a recurring operational task requiring a documented, zero-downtime procedure (dual-secret acceptance window, or accept a brief verification gap). Confirm the package supports overlapping secrets before we need it at 2am.
  • Exit path remains cheap for outbound (config revert) but is now more expensive for inbound: removing the webhook route and event handler is a code change, and any bounce/complaint data we come to rely on must be re-sourced from the replacement provider. Budget for that.

Support

  • Still split between an unverified package maintainer and SendFox support, neither of which substitutes for an ESP SLA on a Tier-1 path. The webhook endpoint raises the stakes: when it breaks, we lose delivery feedback silently.
  • Assign explicit internal ownership. A package that now spans outbound mail, an HTTP endpoint, and a data-sync client will otherwise drift across three teams' blind spots.

Scaling

  • Outbound ceiling remains SendFox's transactional rate limits and plan tier. Establish the numbers before routing volume-bearing flows.
  • Retries change the effective request rate. Three package retries under queue retries can triple outbound call volume during a provider incident — precisely when the provider is least able to absorb it. Cap concurrency and consider jitter.
  • Webhook ingress scales with recipient count, not with our send volume, and arrives in provider-driven bursts. Plan for a queue-backed handler and a store that handles high-cardinality event IDs with a TTL roughly matching the provider's retry window.
  • Pagination makes large contact syncs feasible; ensure callers stream rather than materialise, and schedule syncs off-peak so they do not compete with send throughput for the same quota.

Failure modes

  • Auth failure: unchanged — all sends fail uniformly. Should be loud; verify.
  • Quota exhaustion (429 without Retry-After): not retried by design, so it surfaces as a batch of hard job failures. Needs a dedicated alert distinguishing it from transient errors, or it will be misdiagnosed as a provider outage.
  • Transient 5xx on send: not retried by the client. The job fails unless Laravel queue retries are configured — and those retries are only safe because of Idempotency-Key. If our send sites do not set the key, queue retries risk duplicate emails.
  • Signature verification failures: the nastiest new class. Usually caused by middleware altering the request body, not by forged traffic. Must log distinctly (verification failure vs malformed payload) and must never be silently 200'd away.
  • Dedupe store unavailable: degrades to duplicate event handling (double bounce processing, double suppressions) or dropped events depending on implementation. Confirm fail-open vs fail-closed and choose deliberately.
  • Replay window expiry / clock skew: events rejected as too old during a host clock drift incident. Alert on verification-failure rate, not just on 5xx.
  • Webhook endpoint down: provider retries mask it briefly; beyond that, delivery feedback is lost with no backfill. Treat the endpoint as a monitored service with its own availability target.
  • Lossy header/tag mapping and silent downstream rejection from the baseline remain open and unverified.
  • Abandonment: still mitigated by mailer indirection; now also requires that the webhook handler be kept thin enough to swap.

Ramp-up

  • Outbound developer ramp-up is still about half a day.
  • Webhooks add genuine onboarding: signature verification mechanics, raw-body constraints, dedupe store, event taxonomy, and a replay-testing procedure. Budget 1–2 days for a developer new to the package, plus a security review pass.
  • Operational ramp-up is the larger cost and has grown: runbook must now cover token rotation, webhook secret rotation, webhook endpoint health, dedupe-store health, distinguishing quota exhaustion from outage, retry-budget tuning, and per-provider event-handler gating.
  • Recommend: adopt for a scoped PoC and canary now, and treat the webhook layer as a separate, independently-reviewed decision. v0.3.0 meaningfully reduces the case against this package — retry semantics, idempotency, and bounce visibility were the three strongest objections — but 0.3.0 remains pre-1.0 with no stated support policy, so Tier-1 transactional traffic should still wait for version 1.0, a documented support commitment, or a clean proven canary, whichever comes first.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console