Mobile App

Zapnin

A commitment resolution engine that turns uncertain group plans into confirmed ones — by detecting circular dependencies in conditional responses and resolving them automatically.

The Brief

Group planning has a fundamental problem that no calendar app or group chat has ever solved: circular dependencies. "I'll go if you go." "I'm in if at least 4 people commit." "Only if it's after 7pm and Sarah is coming." These conditional commitments create dependency graphs where resolution is impossible through conversation alone — because everyone is waiting on everyone else. The result is diffusion of responsibility: plans die in WhatsApp groups not from active rejection, but from passive uncertainty. Zapnin was built to replace that chaos with structured commitment collection and automatic resolution. The app collects each person's honest response — IN, OUT, or DEPENDS (with specific conditions) — and runs a resolution algorithm that detects cycles, evaluates conditions, and propagates confirmed commitments through the dependency chain. When circular dependencies are found (A depends on B, B depends on A, both would go if the other goes), the algorithm resolves them: if all parties in the cycle are conditionally willing, they are all confirmed. The plan goes from "maybe" to "confirmed" in minutes, not days. Beyond commitment resolution, Zapnin provides a collaborative planning phase where participants vote on dates, intersect available time windows, and poll for locations via Google Places — all feeding into the planner's final decision. The entire flow replaces thirty confused WhatsApp messages with a structured, resolved, confirmed plan.

The Challenge

The core algorithmic challenge was detecting and resolving circular dependencies in conditional commitments. When participant A depends on B, B depends on C, and C depends on A, you have a strongly connected component in a directed graph — a cycle that cannot be resolved by sequential evaluation. The resolution algorithm needed to handle not just simple person-to-person dependencies, but compound conditions: "I'll go if Sarah AND at least 3 others are in, AND it's after 7pm." This means the dependency graph has multiple edge types (person deps, count thresholds, time constraints) with AND logic combining them. Real-time consistency introduced its own challenges. When multiple participants respond simultaneously, their dependency conditions might reference each other's still-in-flight responses. The resolution pipeline had to handle concurrent mutations without producing inconsistent plan states. Notification design was critical and treacherous — spam kills engagement in group planning apps, but silence kills urgency. We needed batching windows that aggregate rapid-fire responses into single coherent updates without introducing unacceptable delays. The phantom user problem added unexpected complexity: plans often include people who do not have the app. These non-users need SMS invites, and when they eventually install the app, their phone number must be normalized to E.164 format and linked to their existing phantom identity — preserving all prior plan invitations and responses. Finally, failure handling had to be privacy-preserving: when a plan fails because someone's conditions were not met, the system cannot reveal whose specific conditions caused the failure.

Circular dependency detection in conditional commitment graphs using Tarjan's SCC algorithm
Compound dependency conditions combining person deps, count thresholds, and time window constraints
Phantom user architecture for SMS-invited non-users with automatic account linking on signup
Privacy-preserving failure handling that never reveals whose conditions caused a plan to fail

The Approach

Zapnin's architecture was built around the commitment resolution algorithm — everything else (the planning phase, notifications, real-time updates) exists to feed data into or communicate results from the resolution engine. We started by formally modeling the dependency graph: participants as nodes, conditional dependencies as directed edges with typed constraints, and resolution as a graph traversal problem. The backend runs on Node.js with Express, PostgreSQL via Drizzle ORM for the complex relational data, Redis for caching and BullMQ job queues, and Socket.IO for real-time plan updates.

01

Commitment Resolution Algorithm

The resolution engine runs a 6-phase pipeline. Phase 1: Collect all responses and build the dependency graph. Phase 2: Run Tarjan's Strongly Connected Components algorithm (O(V+E)) to detect cycles in the dependency graph. Phase 3: Evaluate each SCC — if all participants in a cycle have DEPENDS responses pointing to each other, they are all resolved to IN (mutual willingness breaks the deadlock). Phase 4: Forward propagation — confirmed participants satisfy dependency conditions for others, potentially triggering cascading resolutions through the graph. Phase 5: Evaluate count thresholds and time constraints against the current resolution state. Phase 6: Final pass to identify unresolvable dependencies and mark the plan as confirmed or failed. The algorithm handles the full expression space: includes/excludes person deps, min_count thresholds, and time window constraints with AND logic.

02

Expressive Dependency Model

Responses are not just IN/OUT/DEPENDS — the DEPENDS type carries a rich condition structure. Person dependencies use includes (must be IN) and excludes (must be OUT). Count thresholds specify min_count (at least N people must be IN). Time windows use after, before, and between constraints. All conditions combine with AND logic: "I'll go if Sarah is in AND at least 3 others AND it's after 7pm" is a single DEPENDS response with three condition components. This expressiveness captures the real nuance of how people actually commit to plans — not binary yes/no, but conditional willingness with specific parameters.

03

Planning Phase System

Before commitment collection, plans go through a collaborative detail phase. Date voting lets participants indicate available dates. Time intersection computes overlapping availability windows across respondents. Location polling integrates Google Places API for venue suggestions with voting. The planner (plan creator) sees aggregated preferences and makes final decisions on date, time, and location. These decisions become the plan's confirmed details, which then feed into time-window dependency evaluation during the commitment resolution phase. The planning phase is optional — planners can skip directly to commitment collection if details are already decided.

04

Phantom User Architecture

Plans frequently include people without the app. The phantom user system sends SMS invites via Twilio to non-app phone numbers, creating phantom user records linked to E.164-normalized phone numbers. Phantom users can respond to plans via SMS with simple keywords (IN, OUT, MAYBE). When a phantom user eventually installs the app and verifies their phone number, the system automatically links their new account to the existing phantom identity — preserving all prior plan invitations, responses, and resolution history. The normalization pipeline handles international format variations, country code inference, and duplicate detection across format variants.

05

Real-Time Resolution Pipeline

The backend uses BullMQ for background job processing with five specialized queues: notification-batch (aggregates rapid responses into batched updates), deadline-checker (monitors plan expiration and triggers final resolution), nudge-scheduler (sends reminders to non-respondents at configurable intervals), sms-sender (Twilio integration for phantom users), and push-sender (Firebase Cloud Messaging for app users). Redis caches active plan states for sub-millisecond reads during resolution. Socket.IO pushes live updates to connected clients — response changes, resolution results, and planning phase updates appear in real-time without polling.

Technical Deep Dive

Zapnin's technical architecture treats group commitment as a graph theory problem implemented with production-grade infrastructure. The resolution algorithm is the intellectual core, but the surrounding systems — real-time updates, notification batching, phantom user management, and conflict detection — are what make it work in the messy reality of social coordination.

Tarjan's SCC Algorithm

The cycle detection engine implements Tarjan's algorithm for finding strongly connected components in the dependency graph. The algorithm runs in O(V+E) time where V is participant count and E is dependency edge count. For each SCC containing more than one node, the resolution rule is evaluated: if every participant in the cycle has a DEPENDS response with conditions satisfied by the other cycle members, all are resolved to IN simultaneously. This handles the classic "I'll go if you go" deadlock that kills plans in group chats. The algorithm also detects and reports unresolvable cycles — cases where conditions within an SCC cannot be mutually satisfied — enabling the system to fail gracefully with privacy-preserving error messages.

PostgreSQL + Drizzle ORM

The data model uses PostgreSQL with Drizzle ORM for type-safe schema definition and queries. Plan responses store dependency conditions as JSONB columns, enabling rich nested structures (person deps, count thresholds, time windows) without schema proliferation. UUID primary keys prevent enumeration attacks on plan and user IDs. Soft deletes with deleted_at timestamps preserve referential integrity for historical resolution data. Phone numbers are stored in E.164 format with a normalization pipeline that handles international variations, making phantom user linking reliable across format differences.

BullMQ Job Pipeline

Five specialized job queues handle the asynchronous workload. The notification-batch queue collects responses within 15-minute windows and aggregates them into single coherent push notifications — preventing the spam that kills engagement in group planning apps. The deadline-checker queue monitors plan expiration timestamps and triggers final resolution attempts when deadlines approach. The nudge-scheduler sends configurable reminders to non-respondents. The sms-sender queue handles Twilio API calls with retry logic and delivery confirmation. The push-sender queue manages Firebase Cloud Messaging with token rotation and failure handling. All queues use Redis-backed persistence for reliability across server restarts.

Socket.IO Real-Time Layer

Socket.IO manages persistent connections organized by plan rooms. When a participant submits or changes a response, the event propagates to all connected plan members in real-time. Resolution results — the moment a plan flips from "pending" to "confirmed" — are broadcast immediately with participant-specific payloads (each person sees their own resolution status and the overall plan status, but not the specific conditions that other participants set). The real-time layer also powers the planning phase: date votes, time preferences, and location suggestions appear live as participants collaborate on details.

Conflict Detection Engine

Zapnin detects scheduling conflicts across a user's active plans. When a participant commits to a plan, the system checks for temporal overlap with other confirmed plans using a 2-hour default duration assumption (configurable per plan). Conflicts trigger warnings but do not block commitment — the system respects user agency while surfacing information. Conflict notifications are privacy-preserving: they tell the user they have a potential overlap, but never reveal the details of the conflicting plan to other participants. Cross-plan analysis also identifies participants who are committed to multiple overlapping plans, surfacing this as a soft warning to planners.

Key Features

Commitment Resolution Engine

The core algorithm that detects circular dependencies in conditional responses and resolves them automatically. "I'll go if you go" cycles are broken by mutual willingness detection. Plans resolve from "maybe" to "confirmed" through graph traversal, not endless back-and-forth.

Structured Conditional Responses

Four response types — IN, OUT, DEPENDS, and no-response — with rich condition modeling. DEPENDS responses support person dependencies, count thresholds, and time window constraints combined with AND logic. The system captures how people actually commit: conditionally, with specific parameters.

Collaborative Planning Phase

Before commitment collection, participants collaborate on plan details: date voting, time intersection, and location polling via Google Places. The planner sees aggregated preferences and makes final decisions. Decided details feed into time-window dependency evaluation during resolution.

Phantom User System

Invite non-app users via SMS. Phantom users receive plan invitations, can respond via SMS keywords, and are automatically linked to full accounts when they install the app — preserving all prior plan history and responses.

Conflict Detection

Cross-plan overlap detection warns users of scheduling conflicts without revealing plan details to other participants. Uses configurable duration assumptions and respects user agency — conflicts are warnings, not blockers.

Smart Notification Batching

15-minute batching windows aggregate rapid-fire responses into single coherent notifications. Nudge reminders target non-respondents at configurable intervals. The system prevents notification spam while maintaining urgency for plan-critical updates.

The Results

Zapnin shipped a commitment resolution engine that fundamentally changes how group plans come together. The resolution algorithm processes dependency graphs with Tarjan's SCC detection, forward propagation through dependency chains, and compound condition evaluation — turning circular "I'll go if you go" deadlocks into confirmed plans. The 6-phase resolution pipeline handles the full expression space of conditional commitments: person dependencies, count thresholds, time windows, and their AND-logic combinations. The phantom user system bridges the app/non-app divide with SMS invites and automatic account linking. The BullMQ job pipeline manages notification batching, deadline monitoring, nudge scheduling, and multi-channel delivery (SMS via Twilio, push via Firebase). Privacy-preserving failure handling ensures that when plans fail, no participant's specific conditions are revealed to others. The architecture proved that the fundamental problem with group planning is not coordination — it is commitment resolution.

<10min
Plan Confirmation Time
6
Resolution Pipeline Phases
4
Response Types
5+
Dependency Operators
2
Notification Channels
Private
Failure Handling

What powers it

React Native + ExpoCross-platform mobile client with real-time Socket.IO integration and push notification handling
Node.js + ExpressBackend API with commitment resolution engine, dependency graph processing, and REST endpoints
PostgreSQL + DrizzleRelational database with JSONB dependency conditions, UUID keys, soft deletes, and E.164 phone normalization
Redis + BullMQCaching layer and job queue system with 5 specialized queues for notifications, deadlines, nudges, SMS, and push
Socket.IOReal-time plan updates, response changes, and resolution notifications with room-based connection management
TwilioSMS delivery for phantom user invitations and keyword-based plan responses from non-app participants
Firebase Cloud MessagingPush notification delivery for app users with token rotation, batched payloads, and delivery confirmation
Google Places APILocation search and venue suggestions during the collaborative planning phase with voting integration