Free tools Windows power users keep installed
One-click scans. No signup required.
To prevent a retried email API request from creating a duplicate send, give each logical email a stable idempotency key, enforce that key with a PostgreSQL unique constraint, and store the send intent in a transactional outbox. Then use the email provider’s idempotency feature if it offers one. PostgreSQL can prevent duplicate local work, but it cannot atomically commit an external provider’s send, so no database-only design can promise exactly-once delivery across both systems.
What an idempotency key does—and does not do
An idempotency key identifies one logical email operation across multiple HTTP attempts. If a client times out and retries, the server can recognize the retry and return the existing operation or result instead of creating another send.
As an Amazon Associate I earn from qualifying purchases.
The key is not a guarantee that an email reached an inbox exactly once. A provider may accept a request just before a worker crashes, leaving PostgreSQL unaware of the acceptance. A later retry must be reconciled with the provider or protected by that provider’s own idempotency mechanism.
Choose a stable key for one logical send
Create or derive the key before the first attempt and save it durably. Every retry of that same send must use the same key; generating a fresh UUID on each HTTP retry defeats deduplication.
#1 Best Overall
Scope the key to the actual operation. Include tenant or account identity where independent customers must not collide. If one business event intentionally produces several emails, distinguish each message stream or template so each logical send gets its own key. A random key is suitable only if it is created once and persisted before the first provider request.
Store enough information to answer a retry consistently: the scoped key, a request fingerprint or equivalent payload identity, the operation state, the provider’s message identifier when known, and the response or result your API promises to return. If a client reuses a key with different content, reject or otherwise handle the mismatch explicitly rather than silently replacing the original operation.
Make PostgreSQL the race-safe local gate
Enforce uniqueness in PostgreSQL with a unique constraint or unique index on the correctly scoped key. PostgreSQL’s unique-index checks handle competing inserts: if a potential conflict is an uncommitted row, PostgreSQL waits for that transaction to commit or abort and checks again. This makes the database constraint a concurrency gate.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
An application-level “look up, then insert” is not equivalent. Two requests can both observe no existing row before either inserts. Let the unique constraint resolve the race, then have the losing request return the stored result or current operation state rather than launch another send. See PostgreSQL’s Index Uniqueness Checks.
Keep the idempotency claim and the business operation within a recovery design. A committed row marked “processing” does not prove that an email was sent. Persist meaningful transitions so a worker can distinguish work that is pending, submitted or ambiguous, and confirmed; the exact states and retention policy depend on the provider and your API contract.
Use a transactional outbox for durable send intent
When a business event should trigger an email, write both the business change and an outbox row in the same PostgreSQL transaction. A separate worker reads pending rows, calls the email provider, and records the result. If the application crashes after committing the business change, the durable outbox still gives a worker something to process.
- Receive the request: validate the stable key and payload, then determine whether this key already represents an operation.
- Commit local intent: in one transaction, apply the business change and insert the idempotency record and outbox work, protected by the appropriate unique constraint.
- Process the outbox: have a worker claim pending work and call the provider using a stable provider idempotency key when supported.
- Record the outcome: save the provider response and message identifier when known; retain ambiguous outcomes for retry or reconciliation rather than treating them as confirmed failures.
- Answer retries consistently: return the existing operation state or saved result for the same key, and do not create a second outbox item.
The unavoidable uncertainty is the gap between provider acceptance and the worker’s local success update. If the provider accepts the email and the worker crashes before recording success, PostgreSQL alone cannot tell the next worker whether a send occurred. Reusing a provider idempotency key can suppress repeated requests according to that provider’s rules; otherwise, use any provider lookup or event mechanisms available and reconcile the ambiguous state.
Use NOTIFY as a wake-up signal, not a queue
PostgreSQL NOTIFY can prompt a worker to check for new outbox rows, but the outbox table—not the notification—is the durable record of pending work. PostgreSQL delays notification delivery until the notifying transaction completes and may fold identical notifications from one transaction into a single notification. A worker should therefore scan the table for work rather than assume one notification corresponds to each row. The official NOTIFY documentation recommends putting large amounts of information in a table and sending the record key.
Check the provider’s idempotency contract
Provider behavior is not uniform. Before relying on a provider feature, verify its documentation for the exact endpoint and account context you use. In particular, check:
- Key scope, such as account, team, endpoint, or operation.
- How long a key is retained and what happens after that period.
- Whether identical payloads replay the original response and whether a changed payload is rejected, ignored, or treated as a new request.
- How concurrent requests using the same key are handled while the first request is in flight.
- Which response codes are cached, including server errors and partial batch outcomes.
- Whether a retry receives the original response and message identifier.
Anypost’s documented behavior
Anypost’s reference describes an optional Idempotency-Key of 1–255 printable ASCII bytes, scoped per team across its single-send and batch endpoints. It says that, within 24 hours, a same-key/same-body request replays the original response; a different body returns 422 idempotency_mismatch; an in-flight duplicate returns 409 idempotency_concurrent; and 5xx responses are not cached. These are Anypost-specific rules, not general email API defaults. Check its API reference before building a contract around them.
Posthaste’s documented behavior
Posthaste documents replay comparison based on the key: reusing a key for different content can return the original message result while discarding the new content. Its guidance is to use a key for one particular message and reuse it across retries. Validate payload identity locally so an accidental key reuse cannot silently produce a misleading result. See Posthaste’s idempotency documentation.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Choose the API response for duplicates deliberately
After the unique constraint identifies a retry, the response should match the contract for the original operation. If the operation is still pending, return its current state or a stable reference to it; if complete, return the saved result. Do not claim success merely because a “processing” row exists, and do not turn an ambiguous provider outcome into a confirmed failure without a way to establish what happened.
Best Value
Also decide how long to retain idempotency records. The right retention period depends on how long clients may retry, the provider’s key retention, and the cost of keeping records. The available provider examples have different replay semantics, so do not assume a provider’s retention window is your database’s safe deletion window.
Failure cases the design should handle
- Two identical requests arrive together: the unique constraint chooses one local operation; the other request reads its state or result.
- The application crashes before the transaction commits: neither the business change nor the outbox intent should be treated as committed.
- The worker crashes before calling the provider: the durable outbox row remains available for another attempt.
- The provider accepts the email but the worker crashes before saving success: mark or retain the outcome as ambiguous and use provider idempotency or reconciliation; local state alone cannot establish delivery.
- A key is submitted with different content: compare the stored fingerprint and apply an explicit mismatch policy rather than overwriting the original request.
- A notification is missed or coalesced: continue scanning the outbox table; notifications are only prompts.
What “exactly once” can reasonably mean
With a stable key and unique constraint, your application can ensure that one scoped key creates at most one local idempotency record and outbox intent. The outbox makes the intent durable, while a provider idempotency feature may suppress repeated provider requests under its documented conditions. Those guarantees are bounded by their respective systems and retention rules. They do not establish universal exactly-once delivery from your database through an external email service to a recipient’s inbox.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problems




