Why Did My App Send That Email or Charge That Customer Twice?

Blog

Why Did My App Send That Email or Charge That Customer Twice?

Matin Amanullahi
  • vibecoding

A customer receives two confirmations. Your dashboard shows two orders. They ask whether they paid twice, and you cannot confidently answer.

Start by separating those three events. A duplicate email, a duplicate order, and a duplicate charge are different failures. They can happen together, but one does not prove the others happened.

Why a successful action can be repeated

Imagine an app sends a payment request, but the connection times out before the reply arrives. The app knows it did not receive a reply. It does not yet know whether the payment succeeded. Sending a fresh request may create another payment.

There is a second path. A payment provider sends your app a notification that the payment completed. Your app sends the confirmation email but fails before acknowledging the notification. The provider sends it again, and the app sends another email.

Both are hypothetical examples of a normal engineering problem: systems need retries because connections and services sometimes fail. The goal is to make repeating an attempt safe, rather than assuming nothing will ever be repeated.

Check the money before changing the code

For an affected order, compare the internal order identifier, the payment provider's payment or charge identifiers, and the email delivery records. Keep the timestamps and error details. Avoid issuing refunds solely because someone received two emails.

If the faulty process is still running, get help containing that specific action. Do not blindly replay every failed job: some may have completed their external action before being marked unsuccessful.

An engineer should reconcile the records so you can distinguish a duplicate display from two real financial transactions. Payment corrections should follow the provider's supported workflow and the verified transaction history.

Give each intended action a stable identity

Engineers call one important safeguard idempotency. In plain language, retrying the same intended operation should not create the effect again.

For an outgoing payment request, the app can reuse an identifier for retries of that same operation, following the payment provider's rules. A genuinely new purchase needs its own identity. Stripe documents how its idempotency keys work, including limitations; they are not a universal substitute for sound order handling.

Incoming notifications need a separate safeguard. Your app should track what it has handled and make the associated business action safe to repeat. Stripe's webhook documentation explicitly covers duplicate deliveries and events arriving out of order.

“Check whether this exists, then create it” can still fail if two workers do that at the same time. Ask the engineer how the database or processing system prevents that race, and how it recovers when an email or payment succeeds but the local record is not updated.

Test repetition on purpose

In the provider's test environment, the engineer should repeat the same event, simulate a delayed response, and interrupt processing at an awkward point. Check the business result: one intended order, the correct payment state, and no unwanted repeated fulfillment.

A disabled button helps prevent accidental double-clicks. It cannot protect against retries elsewhere in the system.

Five prompts to use with your AI coding assistant

Before you begin: Use the same project conversation for all five prompts. Inspect first, approve the plan yourself, and never test against live customer data or secrets.

These prompts assume your assistant can inspect the relevant project. Ask it to say clearly if it cannot. Use the prompts one at a time, reviewing each response before moving to the next step.

1. Map

Map the complete lifecycle of a purchase or email-triggering event, including user action, payment provider, webhooks, queues, workers, database writes, retries, and notifications. Cite the relevant code.

2. Audit

Identify where duplicated or delayed events, concurrent requests, or retries could perform the same business action twice. Verify which protections already exist. Do not assume two emails mean two charges.

3. Plan

Propose a testable design using appropriate idempotency keys, stored event states, uniqueness rules, and transactional boundaries. Preserve recovery for failed legitimate operations.

4. Challenge

Simulate duplicate webhooks, network timeouts, simultaneous workers, and late responses with synthetic events in a sandbox. Explain any remaining risks and amend the plan.

5. Implement

After approval, implement the safeguards in a test environment and run duplicate-event and retry tests. Provide evidence of one intended business outcome and no blocked valid transactions; no live charges or customer messages.

You are ready to resume normal processing when the cause is understood, affected records are reconciled, and repeat handling has been demonstrated. DitchNow can help with reliability and integration repairs. Bring an example of the repeated action to a founder call, with customer details removed.


About DitchNow
DitchNow is based in Haarlem, Netherlands, and helps founders and teams fix and extend software and put AI to work. Meet DitchNow.

Start here

Start with a founder call

No prep required. We respond within one business day.

hello@ditchnow.com