SendDart in PHP Applications: Separate Web Requests From Queue Workers
Build a PHP email integration that handles SDK exceptions, long-running worker configuration and transaction-aware notification jobs.
TL;DR
- Decouple real-time controllers from provider calls: record intent in a business notification service and let workers perform external sends so web requests and long-running workers remain independent during deployment and recovery.
- Use durable outbox records and minimal payloads so workers load protected resources at execution time, preserve machine-readable reasons, and avoid serializing clients, streams, or large confidential attachments into the queue.
- Treat runtime and rollout as testable limits: confirm SDK/runtime compatibility in the worker image and run integration tests in that environment to exercise rollbacks, restarts, credential rotation, and reconciliation.
PHP has more than one execution lifetime
A PHP web request and a long-running queue worker can load the same application code but behave differently during deployment and recovery. The request may end quickly; the worker may retain configuration and memory across many jobs. An email integration should account for both lifetimes.
Begin with a business notification service instead of calling the provider from every controller. The service records why a message is needed and delegates external execution to a worker. That boundary lets application code remain independent of provider-specific exceptions and retry details.
This article focuses on architecture and release checks. It does not assume that a Laravel job configuration or PHP runtime setting is correct for every hosting environment; verify the versions and process model your application actually uses.
Review runtime and SDK requirements
The SendDart PHP SDK documents its PHP and extension requirements and returns decoded response arrays on success. Non-success API responses raise SendDartException with structured status and name accessors. Confirm the package in your production image, not only on a developer laptop. SendDart PHP SDK.
Check the installed extensions, dependency lockfile and deployment build. A package can install successfully while a worker image lacks a required extension. Include a startup or deployment check that confirms the runtime can initialize the intended client without exposing its credential.
Keep provider response translation in one adapter. Business code should not depend on matching exception message text, which may change. Preserve the machine-readable reason and any recovery evidence in the notification record.
Commit intent before dispatching work
If the application updates an order and sends a confirmation inside the same unfinished database transaction, a rollback can leave the customer with a misleading email. If it commits and then performs a separate queue call, a process failure can lose the notification request.
Use a durable notification or outbox record with the business transaction where appropriate. The queue job can refer to that record, and a dispatcher can recover retained work if signaling fails. Framework facilities that delay dispatch until commit help with ordering, but their precise behavior should be checked against the selected queue backend.
Laravel documents queue dispatch, worker operation and transaction-related behavior. Use those facilities deliberately rather than assuming a generic dispatch call solves every failure boundary. Laravel queue documentation.
Keep job payloads constrained
Pass stable record identifiers or immutable, minimal data to the worker. Do not serialize a provider client, open stream or large confidential attachment into a broadly visible queue payload. Load protected resources under the correct application scope at execution time.
Freeze the content version associated with the notification. A retry should not silently use a new template or changed invoice amount under the old operation key. Recheck current suppression and sender authorization separately because those policies can change after the job is created.
If a resource no longer exists or the message has expired, record a deliberate skipped outcome. Do not turn every missing model into a generic retry that runs until the queue's maximum attempts are exhausted.
Coordinate SDK and worker retry budgets
The SDK and queue framework can both retry. Review the combined number of attempts and time budget. A job timeout should leave room for the provider request to return and for the worker to persist evidence; otherwise termination can create ambiguous outcomes repeatedly.
For supported SendDart sending operations, retain the same idempotency key and payload during recovery. If a response includes original message identity or partial progress, reconcile it before creating new work. A queue retry counter does not tell you whether the remote operation already happened.
Separate known rejection from uncertainty. A missing credential or invalid payload may require repair. A temporary rate restriction may justify delayed work. A connection interruption can require investigation of the original attempt instead of another fresh submission.
Reload configuration through the real worker lifecycle
Long-running workers may not observe a changed environment or deployment configuration until they restart through the framework's supported process. Plan key rotation and application releases around that behavior. Updating the web application's configuration is not proof that every worker now uses it.
Record a non-secret configuration version or deployment identifier with operational evidence. Verify a controlled message after the worker rollout. Do not print the API key to demonstrate that the new process received it.
If customers supply separate credentials, bind each job to the correct protected configuration record. Test two tenants concurrently and ensure neither can select the other's sender or secret. Avoid shared mutable configuration that crosses job boundaries.
Verify inbound callbacks before application mutation
Create a webhook route with the raw-body access required by the signature verifier. Authenticate and validate the event before updating notification or recipient records. Keep the route's parsing and middleware behavior under integration tests.
Persist a unique event observation and process side effects idempotently. A duplicate delivery or complaint callback should not create duplicate alerts or reverse a newer sending restriction. Correlate through stored provider message ownership, not a guessed address match.
Test callbacks that arrive before the send result is committed and callbacks for unknown messages. A bounded reconciliation path is more reliable than dropping evidence or mutating arbitrary records.
Test deployment and recovery together
Run the integration tests in the actual worker image or an equivalent environment. Exercise a transaction rollback, a duplicate job, a worker restart after acceptance and a credential rotation. Confirm that the application can explain each notification's state afterward.
Use fake transports for deterministic failure cases and controlled simulator or mailbox addresses for integration proof. Keep those claims separate in the release report.
A dependable PHP integration respects the difference between request execution and worker execution. Durable intent, explicit exception handling and a tested configuration lifecycle keep email behavior stable as the application grows.
Related reading: Best Email API: Choose With a Production Acceptance Test.
Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.