DEV Community

Cover image for Dev Log: 2026-07-03 — idempotent syncs, mail you can trace, and drain-not-delete
Nasrul Hazim Bin Mohamad
Nasrul Hazim Bin Mohamad

Posted on

Dev Log: 2026-07-03 — idempotent syncs, mail you can trace, and drain-not-delete

TL;DR

  • Sync must be idempotent. Re-running a gateway sync should converge, not explode on the second pass — adopt what already exists instead of erroring on a UNIQUE conflict.
  • Paginate for real. A list API that caps at 100 will lie to you the moment you have 101 records. Fetch every page before you diff.
  • Outgoing mail can be observable without a third-party service — Laravel's mail events give you delivered/opened/clicked hooks.
  • Elsewhere: ITIL ticket types + major-incident handling on a support desk, a Flutter mobile client wired to a REST v1 API, and a breadcrumb bug that was really an ownership bug.

The gateway thread: make sync boring

Most of today ran through an API-gateway management app. The sync fixes got their own write-up (the idempotent-sync post in this series).

Short version: syncing config into a gateway is a converge operation, not an insert. If a target already exists, adopt it on the 409 instead of throwing. If a list endpoint pages, read all the pages — a partial read makes your diff hallucinate deletions. And a scoped plugin must never silently widen to global scope; scope is identity, not a detail you can drop.

Mail you can actually trace

An identity portal picked up delivered/opened/clicked tracking on outgoing mail — no external ESP, just Laravel's own MessageSending / MessageSent events plus a tracking pixel and link rewriting (full write-up of its own). The nice part: it's a runtime toggle, so you can turn tracking off per-environment without a deploy.

Support desk: incidents are a different shape than tickets

A support platform got ITIL ticket types and major-incident management. The lesson underneath: a major incident isn't just a high-priority ticket. It has its own lifecycle — declare, coordinate, stand down — and its own audience. Modelling it as a first-class thing (declare/stand-down actions, internal work items) beats overloading the priority field and hoping.

Same platform also got emoji reactions, reply editing, soft-delete with trash recovery, a notifications inbox, and analytics wired end-to-end. A companion Flutter mobile client came online too, talking to a fresh REST v1 API for auth, tickets, and attachments.

Change Why it matters
Soft-delete + trash restore Deletion should be reversible; a confirm step guards the irreversible part
Notifications inbox + unread badge Pull the user back without email noise
REST v1 for the mobile app One versioned contract for web and mobile, not two divergent APIs

The small one that taught the most

An importer package had hard-coded breadcrumbs in its views. The fix was to delete them — the host app already renders breadcrumbs globally. Rendering them twice isn't a styling bug, it's a boundary violation: the package overstepped into layout that isn't its job.

Takeaway

Three ideas carried the day: sync converges, so make it idempotent; observability can be built from framework primitives before you reach for a service; and know where a package's job ends — don't render what the host already owns.

Top comments (0)