A repeated event ID is not enough to call a request a duplicate A retry arrives with an event ID ...A repeated event ID is not enough to call a request a duplicate A retry arrives with an event ID ...
The network for creativity
Join 1.25M professional creatives like you
Connect with clients, get discovered, and run your business 100% commission-free
Creatives on Contra have earned over $150M and we are just getting started
A repeated event ID is not enough to call a request a duplicate
A retry arrives with an event ID already in the database. Should the importer ignore it? I would compare its contents first. The same ID can carry a changed request, and silently dropping that request loses information worth investigating.
Here is the smallest SQLite example of the problem:
CREATE TABLE requests (id TEXT PRIMARY KEY, body TEXT NOT NULL);
INSERT OR IGNORE INTO requests VALUES ('same-id', 'Blue notebook');
INSERT OR IGNORE INTO requests VALUES ('same-id', 'Red notebook');
SELECT * FROM requests;
-- same-id | Blue notebook
The unique key prevented a second row. It did not preserve the attempted change. I ran this example alongside a Python event importer to examine the difference. This is a self-initiated experiment with synthetic data, not a client incident.
CHOOSE WHAT COUNTS AS THE SAME REQUEST
The importer accepts three string fields: event_id, email and request. It validates the input, then compares a SHA-256 fingerprint with the one stored for that ID. The fingerprint covers all three fields, serialized with:
Sorting keys makes JSON object order irrelevant. It does not make every business-equivalent request identical. In a separate test, reversing the keys produced a duplicate; adding a trailing space to the request produced a conflict. Giving the unchanged request a new event ID created another event.
Those results define this importer's identity rule. It recognizes retries under a stable ID, not duplicate orders across different IDs. I would settle that distinction with the event producer before adding normalization: trimming or lowercasing a value can change what the application means by “same.”
KEEP THE DECISION AND THE QUEUED WORK TOGETHER
For a previously unseen valid ID, the importer writes an events row and a matching outbox row inside one BEGIN IMMEDIATE transaction. The outbox has a unique event_id, a foreign key to events, and a LOCAL_PENDING status. No worker sends it anywhere in this experiment.
For an existing ID, equal fingerprints return “duplicate” without another outbox row. Different fingerprints retain the original event and insert a conflict record for review. Repeating that same conflict does not add another record: the conflict table is unique on event_id and attempted_hash.
There is a limitation I would fix before calling this an audit trail: the conflict record stores the attempted hash and a reason, not the changed body. It proves a different fingerprint was observed, but a reviewer needs the original source to inspect the attempted change. A real intake system needs an explicit retention policy for that evidence.
SQLite documents that BEGIN IMMEDIATE starts a write transaction immediately and can fail with SQLITE_BUSY if another connection is already writing. This demo has no busy-retry policy or concurrent-load test. That would need separate work before using the pattern under contention.
READ THE SECOND PASS CAREFULLY
I replayed a fixture of 16 inputs twice on Python 3.14.5 and SQLite 3.50.4. The first pass returned 8 created, 3 duplicate, 2 conflict and 3 rejected. The second returned 0 created, 11 duplicate, 2 conflict and 3 rejected.
Eleven duplicates does not mean eleven duplicate rows. The eight originally new events became duplicates on replay, alongside the three original retries. After either pass, SQL counts were unchanged: 8 events, 8 outbox rows, 2 conflict records and 3 rejection records.
Queries also found zero repeated outbox IDs, zero events without an outbox row, and zero payload mismatches between those tables. These checks support the result for this fixture; they are not a general proof for arbitrary inputs.
BREAK THE TRANSACTION AT THE USEFUL POINT
The failure test deliberately raises an exception after inserting a new event but before inserting its outbox row. The exception handler rolls back. Both counts for that test ID remained zero, and the database counts matched the pre-failure snapshot.
A separate file-backed test closed and reopened the connection, then replayed a committed event. It still returned “duplicate.” That demonstrates the decision survives a connection restart. Neither test simulates a power failure or a process killed during a commit.
I would keep one further boundary explicit: a committed outbox row is pending work, not proof of delivery. A future worker could send successfully and crash before recording success. Handling that case needs a delivery and retry design, usually with a stable key the receiver can recognize. The local replay results say nothing about an external receiver.
My practical choice is to record changed requests as conflicts, then keep event acceptance and local queue creation in one transaction. The useful guarantee is narrow enough to test: replaying the accepted fixture adds no queued work, and the injected application failure leaves neither half of a new event behind.
Experiment note: synthetic example.com addresses; no network calls or external messages from the test harness. Article and code prepared with AI assistance; reported outputs were verified by executing the examples.
I'm testing a $79 report for Shopify merchants who want to see exactly what changed between their original product CSV and a proposed price-update file. It separates intended price changes from changes to other fields, so you can review both before deciding what to do next.
Here's a simple synthetic example: a mug's price changes from $20 to $22 as intended, but the proposed CSV also blanks its Vendor and changes its Status from active to draft. The report lists those differences separately from the price change. This is an invented demonstration, not client work or a customer result.
The proposed $79 report covers up to 1,000 matching products, with one row and one default variant per product, using supported columns only. It uses deterministic comparison with AI-assisted explanations.
This is a read-only comparison. It does not access your store, execute an import, correct files or certify that an import is safe.
Have both CSV files ready for an upcoming price update? Message me on Contra with your product count and intended change, and let me know whether you would request the report at $79 if scope, availability and terms can be confirmed. Start with a description only; don't send confidential files or store access. An inquiry does not place an order or request payment.
Catching accidental changes outside the price column is the part a plain spreadsheet compare never shows clearly. How are you testing the $79 price, DMs to merchants or a landing page?
Your Logistics Team Shouldn't Have to Chase Every Shipment Manually. 🚢
A shipment is delayed. Your customer wants an update. Your operations team is checking carrier portals, copying tracking numbers into spreadsheets, and sending the same status emails repeatedly.
By the time everyone has the right information, the next shipment needs attention.
The problem isn't a lack of effort. It's disconnected logistics operations.
Common challenges:
Shipment updates scattered across multiple carrier systems.
Delayed shipments discovered too late.
Manual customer status emails.
Tracking spreadsheets that quickly become outdated.
Repetitive follow-ups between operations and customer service.
Limited visibility into shipment exceptions.
The solution: An automated logistics operations workflow built with n8n.
I can design an automation that connects carrier tracking APIs, your shipment database, and customer communication channels.
Instead of manually checking every shipment, your team receives structured updates when something actually needs attention.
What the automation handles:
📦 Shipment Tracking — Retrieve updates from supported carrier APIs.
📊 Operations Visibility — Maintain synchronized shipment records and exception histories.
🔁 Escalation Management — Notify the responsible team when an exception requires action.
The goal: Less repetitive tracking work, faster exception awareness, and more consistent customer communication.
Two ways we can work together:
Option 1: Already have carrier APIs, a TMS, or a defined tracking workflow? Share your requirements, and I'll help design the integration.
Option 2: Not sure which logistics tasks should be automated? We can map your shipment lifecycle and identify the highest-value starting point.
Make your logistics operations easier to manage as shipment volume grows.
Workflow architecture
Scheduled Trigger → Carrier API Request → Normalize Tracking Events → Match Shipment Records → Detect Status Changes → Route Exceptions → AI Summary → Customer Email / Operations Alert → Database Update
Implementation: Authenticate carrier integrations, normalize differing event codes, compare timestamps and event IDs, prevent duplicate alerts, route exceptions by severity, and log delivery failures for retry.
Benefits: Reduced manual tracking, clearer exception ownership, more timely customer updates, and an auditable shipment history.
Result: A centralized shipment monitoring workflow that allows operations teams to focus on resolving exceptions instead of repeatedly checking tracking portals.
Normalizing carrier event codes is the unglamorous part that makes or breaks this. Ops will only trust exception alerts if they can see why a shipment was flagged, so having the AI summary cite the raw carrier event matters.