SendDart in FastAPI: Choose Background Work With a Clear Lifetime
Integrate email with FastAPI by separating request dependencies, durable jobs and synchronous SDK work from the asynchronous request lifecycle.
TL;DR
- Decide between FastAPI BackgroundTasks and durable workers up front: classify each notification’s required durability and record the business intent before selecting an execution mechanism.
- Avoid passing request-scoped objects into long-lived jobs: send stable identifiers to a worker that reconstructs context, and retain an outbox-style record to recover from queue signal failures.
- Validate success by testing process boundaries rather than endpoint responses: simulate termination, duplicate delivery, and expired notifications to confirm workers and reconciliation behave as intended.
Background execution and durable execution are different
FastAPI provides a convenient BackgroundTasks mechanism for work associated with a response. That can be useful, but it should not be confused with a persistent job system that survives process termination. Email tied to an important business event needs an explicit durability decision.
Begin by classifying the notification. A best-effort internal diagnostic may tolerate a different failure model from an account reset or paid-order receipt. Record the requirement before selecting the execution mechanism simply because it is easy to call from a route.
The framework documentation describes BackgroundTasks and notes situations where larger external task tools may be appropriate. Use that guidance with your application's reliability needs, rather than treating every operation after a response as guaranteed background delivery. FastAPI background tasks.
Keep request dependencies out of long-lived jobs
A request may create a database session, load an authenticated user and establish a tenant context. Those objects have lifetimes tied to the request. A durable worker should receive stable identifiers and reconstruct the authorized context through its own supported resources.
Do not place an open database session, request object or live SDK client into a serialized task payload. Pass a notification ID and load the necessary data in the worker. This also avoids capturing more personal information than the task needs.
Record the business event and intended notification before returning success. If the queue signal fails after the database commit, a retained outbox record gives the system a way to recover. An in-memory callback alone does not provide that recovery record.
Account for the SDK's blocking behavior
The documented SendDart Python SDK uses a synchronous standard-library HTTP implementation. Calling it directly inside an asynchronous path requires thought about execution and concurrency. Do not assume that adding async to the route makes a blocking client nonblocking. SendDart Python SDK.
A separate worker often provides the clearest boundary for slow external sends. If the application deliberately uses thread offloading, bound the concurrency and understand shutdown behavior. Threads help overlap work but do not replace durable notification storage.
Set timeouts with room to persist the result. The worker's overall execution budget includes rendering, provider retries and database updates. A client timeout equal to the host's termination deadline can leave the operation uncertain without a recorded outcome.
Treat validation as a business constraint
FastAPI's request models can validate structure, but the application still needs authorization and purpose checks. A syntactically valid address does not mean the caller may send arbitrary content to it.
For an invitation endpoint, derive the sender and template from the authorized organization. Limit user-controlled fields and enforce an invitation policy. Do not accept a provider base URL or arbitrary attachment fetch destination from the request.
Return a response that matches the state you have established. If durable intent was created, report that the notification is queued. Do not label the operation delivered merely because the request handler completed or the provider accepted the payload.
Make error classification explicit
Handle SendDartError using its structured status and reason fields. A rejected payload should become an actionable failure rather than an endless retry. A capacity rejection can be scheduled according to a bounded policy. A transport interruption may require an uncertain state.
Preserve the original operation key and immutable payload for supported idempotent recovery. The worker should not generate a new key every time the task framework repeats execution. Store any provider message identity even when the response also indicates a problem.
Keep exception detail out of public responses when it contains sensitive operational data. Give clients a safe status and support reference; retain restricted diagnostics for the team that operates the service.
Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.
Verify callbacks independently of user requests
A webhook route has a different authentication model from an ordinary signed-in user request. Verify the provider's signature against the original body, then validate and persist the event. Do not reuse a dependency that assumes a browser session is the source of authority.
Use the stored integration and message ownership to determine tenant scope. A valid callback for one project should not update another project's notification. Deduplicate observations at a durable boundary and make downstream suppression or analytics actions safe to repeat.
If the event arrives before the sending worker commits its response, retain a controlled unmatched record and reconcile it later. Guessing from the recipient address can attach evidence to the wrong notification.
Test process boundaries, not only endpoint responses
An endpoint test that receives 200 or 202 proves little about later work. Test that the notification record exists, the worker claims it correctly and the provider result produces the intended state.
Simulate process termination before submission and after provider acceptance. Test duplicate task delivery and an expired notification waiting in the queue. Verify that shutdown does not convert uncertain work into an automatic fresh send.
Use local fake transports for deterministic failures and documented simulator recipients for integration events. A separate controlled live canary can validate sender configuration, but it should not replace the failure tests.
The strongest FastAPI email design makes lifetimes explicit. Requests authorize and retain intent, workers own bounded external operations, and callbacks add authenticated evidence. That structure remains understandable even when the original request has long since ended.
Related reading: Best Email API: Choose With a Production Acceptance Test.