Back to browse
Verity – I got tired of debugging duplicate emails after job restarts

Verity – I got tired of debugging duplicate emails after job restarts

by shineDaPoker·Feb 26, 2026·1 point·0 comments

AI Analysis

●●●BangerWizardrySolve My Problem

Exactly-once semantics for external APIs without replacing your workflow engine.

Strengths
  • Solves a real production pain (duplicate charges, emails) that workflow engines (Temporal) alone don't address
  • Fence token + observe/act pattern is genuinely clever distributed systems design
  • Clear positioning as complementary to Temporal/Trigger.dev, not competitive—honest market positioning
Weaknesses
  • Requires SaaS dependency for coordination; adds latency and monthly costs to every protected action
  • Unverified in production at scale; adoption risk depends on how Temporal/Inngest communities adopt it
Target Audience

Backend engineers building job platforms, payments systems, and autonomous agents

Similar To

Temporal · Trigger.dev · AWS Lambda with distributed transactions

Post Description

Hey HN,

I built Verity after debugging a production issue where our job platform was sending duplicate emails to tens of thousands of hotel guests.

We were running automated checkout for a chain of hotels across Germany, Spain, and New York. The process had multiple steps: validate reservations, generate folios, check guests out, send receipt emails.

The job would run for 25+ minutes processing thousands of reservations. Sometimes it would time out at 30 minutes. The platform would see the job didn't complete and restart it from the beginning.

The problem: some operations were idempotent through the provider (like creating a folio with the same ID), but others weren't (like sending emails). Guests would get duplicate receipt emails. We'd generate the same reports twice.

It took a week of adding logs to figure out what was happening. The restart was invisible to our code — it just looked like the job was running twice.

I built Verity to solve this: wrap each critical step or side effect so it's safe to restart anywhere:

// Check out guest await verity.protect(`checkout-${reservationId}`, { observe: () => hotel.getReservation(reservationId), act: () => hotel.checkout(reservationId) });

// Send receipt (not idempotent in the email service) await verity.protect(`receipt-${reservationId}`, { observe: () => checkIfEmailSent(reservationId), act: () => sendReceiptEmail(guest.email, folio) });

If the job restarts after sending the email but before finishing, the retry checks first (observe) and skips re-sending.

It uses fencing tokens to prevent zombie workers from committing stale results if they wake up after being timed out. Full audit trail of what ran, what was skipped, and why.

The same pattern applies to any system that retries without human oversight — background jobs, workflow engines like Temporal, or AI agents that make autonomous decisions. If your code crashes after calling an external API, the retry needs to know what already succeeded.

Works with Temporal, Trigger.dev, plain job queues, or standalone.

Still early. Looking for people who've hit similar issues to try it and tell me what's wrong. Free for design partners.

Live: https://useverity.io Docs: https://useverity.io/docs Install: npm install @verityinc/sdk or pip install verityinc-sdk

Happy to answer questions about the design or share more war stories from that debugging week.

Similar Projects