KV check
Was this parsed purchase already sent?
Stops repeated API submissions, schedules retries, and tracks parsed purchases.
How the club reads TooCOOL receipts and prevents the same order from being processed twice.
A purchase receipt arrives by email, not through a trusted payment webhook. The system checks the sender and recipient, reads the email and its PDF, and makes sure the same purchase is not processed twice.
Two services share the work. The Email Worker reads the message. The API processes the order. Both save progress so a retry does not repeat finished work.
System flow
The Email Worker reads receipts and retries temporary failures. D1 records the result. Stable provider IDs make repeated calls safe.
Read and claim the receipt
Complete a membership purchase
Complete another purchase
Handle a failure
Cloudflare Email Routing forwards messages for the configured purchase address to the Email Worker. The Worker receives the envelope and raw message stream through its email handler.
Before reading the body, it checks:
The Worker enforces the raw-message byte limit as it reads the stream. After it parses the email’s MIME structure, it checks the attachment count, PDF size, and whether the message contains a supported purchase. MIME is the email format that separates headers, text, HTML, and attachments. The Worker checks its API and KV bindings later, when the parsed purchase enters the processing flow.
These early checks stop the Worker from spending time on a message it cannot use.
The Worker reads the message in three steps.
postal-mimepostal-mime splits the raw email into headers, text, HTML, and attachments. It handles MIME boundaries, text encodings, and filenames so the purchase parser does not have to.
unpdfTooCOOL receipts may store the order details in a PDF. unpdf extracts the text so the Worker does not treat raw PDF bytes as text.
The code uses unpdf, not pdfjs-dist. Check the right package when changing the parser or debugging the production bundle.
The TooCOOL parser turns the receipt text into an order ID, purchaser details, line items, quantities, and known membership, film, or print products.
Parsing does not create a membership or send a notification. It only prepares a limited request for the API.
The API checks the parsed order again before it creates a key, sends an email, or updates Discord.
The same email can arrive more than once. A provider may retry after a timeout, a forwarding rule may send it again, or the Worker may lose a response after finishing part of the work.
After MIME, PDF, and TooCOOL parsing, the RECEIPT_DEDUPE KV namespace tracks each normalized purchase. A normalized purchase is the clean, standard order data made by the parser. Its stable purchase key lets the Worker:
KV protects submission of the normalized purchase and retries to the API. It does not deduplicate the raw email or prove that the API finished the order.
The API receives the parsed receipt through a private service-binding route. Before it sends an email, creates an activation key, or starts Discord work, it claims an idempotency record in D1’s receipt_fulfillment table. Idempotency means a retry can use the same request without creating the result twice.
This D1 record sits next to the order result, so it is the final duplicate check. Two emails can produce the same parsed purchase, but only one request can own the fulfillment.
KV check
Stops repeated API submissions, schedules retries, and tracks parsed purchases.
D1 check
Stops duplicate activation keys, emails, membership changes, and Discord notifications.
D1 owns fulfillment, but an outside service can finish a call just before the API saves that step. The code uses stable IDs to make retries safe:
Idempotency-Key derived from the D1 fulfillment row.If the provider accepts a call but the next D1 marker write is lost, the retry uses the same ID. It does not create a second email or Discord message.
For a known membership product, the API:
The purchaser redeems the key through the normal account flow. The API still controls redemption, membership, expiry, and Discord role updates. The email parser cannot change them by itself.
The sender uses the club’s membership address. The Resend API key stays in the Worker secret settings. It is not in the repository or website.
Film and print purchases do not create a membership key. The parser identifies those items, the API claims the fulfillment, and the API sends the right Discord notification to club staff.
The duplicate checks still apply. A receipt sent twice must not create two “new film order” messages.
Some failures can be retried. Others should stop.
| Failure type | Example | Worker behavior |
|---|---|---|
| Temporary | API binding timeout, provider failure, or short storage outage | Save a KV retry record and try again after the configured delay. |
| Email rejection | Wrong recipient, unsupported sender, missing PDF, or no supported purchase | Reject the Email Routing message. These paths do not create a final KV record. |
| Permanent parsed-purchase failure | A parsed purchase cannot be processed after it enters the flow | Save the final KV result and stop automatic retries. |
| Duplicate | The parsed purchase or D1 fulfillment is already complete | Return the saved result without repeating emails, keys, or notifications. |
| In progress | Another delivery holds the active lease | Do not start a second parse or fulfillment. |
The Email Worker has a scheduled job, or cron, that checks for eligible KV retries every five minutes. It is separate from the platform Scheduler Worker, which runs club jobs every thirty minutes. The Email Worker owns receipt retry timing.
A service can finish the work even if the caller never receives its response.
For example, the API may create an activation key and Resend may accept the email, but the next email_sent_at write or the final response may be lost. A retry returns to the same D1 fulfillment and activation key. It calls Resend with the same idempotency key, so Resend can return the first send instead of delivering another email. A Discord retry uses the same fixed nonce to avoid a second message.
KV only tracks the delivery attempt. D1 tracks the saved membership result. Stable provider IDs cover the gap between an outside service finishing its work and D1 saving that step.
The fulfillment route is private. It requires the Email Worker’s identity and internal token through the service binding. A public browser cannot submit a fake receipt to get a membership key.
The API checks the parsed fields again. It uses parameterized database operations, keeps secrets out of error responses, and signs each Discord event before sending it.
Live Email Routing rules and production sender settings are stored outside the repository. The code defines the expected address, binding, parsing flow, and checks. This page does not confirm that the live mailbox rule was checked when it was written.