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.
MAIL_MAILER=sendfox) plus a listening endpoint. The package registers POST /sendfox/webhook and emits a single WebhookReceived event per verified event, so bounce/complaint handling is a product-engineering task (a few lines in a listener), not an integration project. Consolidation is no longer strictly a commercial/deliverability gamble — you can now observe what happens.bounced and related events arrive in-app, which means "do we maintain our own suppression list?" moves from blocked by tooling to a scoped piece of work. This is the decision the release most changes: the team can own reputation hygiene on SendFox instead of outsourcing it by necessity.Idempotency-Key on a Mailable is the new product-relevant primitive — it makes queued notification jobs safe to retry, which is the difference between "fire and hope" and "safe to enqueue" for user-facing email.new SendFox($key, maxRetries: 5) or disabled with 0; 429 with Retry-After is honored, 5xx/network errors retry only on idempotent methods. Note the deliberate gap: a 429 without Retry-After (exhausted monthly quota) and a POST that hits 5xx are not retried — so the send path relies on Laravel's queue retries plus the idempotency key to stay exactly-once. That's a reasonable design, but it means queue-level retry policy is now a product-visible decision, not an implementation detail.$sendfox->paginate('/contacts', ['per_page' => 1000]) with lazy page fetching makes contact/audience sync, suppression export, and "who's subscribed?" reconciliation cheap to build — supporting roadmap items like CRM sync or a self-serve preference center without a data-engineering ticket.Mail facade, config-level entry and exit. Retries and idempotency keys are additive and don't create coupling; the webhook endpoint is the only new surface the app must own, and it's a single route plus a listener.Caveats to weigh in the decision: Still pre-1.0 (now v0.3.0) with a single maintainer and no track record — pin the version. SendFox remains marketing-email-first: transactional deliverability, SLA, and the depth of its bounce/complaint taxonomy are unproven, and the webhook payload's event vocabulary is not documented here beyond bounced. Still absent: batch/bulk transactional sending, server-side templating, inbound routing, dedicated IPs, suppression-list management (you receive events; you own the list), and any stated SOC 2 / DPA posture. Note the webhook dedupe is per-event-ID within the package's handling — confirm the dedupe store and its retention, since a restart or a queue flush mid-handler could re-deliver.
Adopt when:
Idempotency-Key support turns at-least-once queue semantics into effectively-once sends — this is what makes the package viable for notifications triggered from jobs, retries, or webhook handlers.Look elsewhere when:
POST hitting 5xx is not retried by the client, you must own queue retry policy and verify the idempotency key is honored end-to-end — if you can't invest in that, a provider with transactional-grade retry semantics is the safer buy.Retry-After) is a realistic production event; nothing here softens that failure.For executives: The integration now does the things we said it was missing. It receives delivery events from SendFox (bounces, and whatever else we subscribe to), verifies they genuinely came from SendFox, and won't process the same event twice. It also retries transient failures and can guarantee a retried job doesn't send the same email to a customer twice — which was the real reason we kept a separate transactional vendor. Position: a pilot for non-critical email with real observability now, still not a drop-in replacement for a revenue-critical provider until we have deliverability data and a support story. Cost story is unchanged and better justified: one vendor instead of two, reversible via config.
For engineering: Three capabilities landed. (1) Webhooks: Webhook::verify($rawBody, $signatureHeader, $secret) does HMAC over X-SendFox-Signature and rejects timestamps older than 5 minutes; set SENDFOX_WEBHOOK_SECRET and the package registers POST /sendfox/webhook; each verified event fires Dyaskur\SendFox\WebhookReceived exactly once with retries of the same event ID skipped — listen for $event->type === 'bounced'. Confirm where that dedupe state lives before you depend on it. (2) Retries: up to 3 by default, honoring 429 + Retry-After and retrying 5xx/network only on idempotent methods; tune with maxRetries: or disable with 0. Deliberate non-retries to design around: quota-exhausted 429 and POST 5xx — so keep your queue retry/backoff and dead-letter path, and set Idempotency-Key on queued Mailables so those retries are safe. (3) paginate() yields across all pages with lazy fetching — use it for contact/suppression sync. Still pin the version, wrap behind mailer config, keep a tested fallback mailer, and trail on low-risk notifications with failure instrumentation before widening scope.
How can I help you explore Laravel packages today?