Loop is a warm introduction tracker that watches email threads and follows up weeks later to ask what happened. You CC loop@getlooped.cc on an intro email, and the service checks back with both parties after a delay. The product pitch is simple, but the plumbing underneath exposes a cluster of stateful automation problems: how do you track multi-party workflows across systems you don’t control, when do you send notifications without spamming, and what happens when email state conflicts with CRM state?
This is not a CRM. Loop positions itself as a “quiet companion” with no signup, no inbox access, and no database you have to populate. That constraint forces interesting architectural choices about state inference, notification scheduling, and privacy boundaries.
State Model for Multi-Party Workflows
A warm intro has at least three actors: the introducer, the person being introduced, and the recipient. Each actor can respond (or not) at different times, creating a partial-update problem. Loop needs to track:
- Intro sent: timestamp, participants, thread ID
- Response from party A: detected or inferred
- Response from party B: detected or inferred
- Follow-up scheduled: when to check in
- Follow-up sent: timestamp, delivery status
- Outcome reported: meeting scheduled, deal closed, no response
The state machine is simple in theory but messy in practice because Loop has limited observability. It sees only the emails where it was CC’d. If the introducer forwards the thread without CC’ing Loop, or if the parties move to Slack or a calendar invite, Loop loses the thread.
Detection Strategies
Loop has three options for detecting progress:
- Email polling: parse replies in the thread where Loop was CC’d
- Calendar webhooks: watch for meeting invites between the parties
- CRM sync: pull status updates from Salesforce, HubSpot, or Attio
- Manual input: ask the introducer to report back
The Show HN post and landing page suggest Loop uses email-only detection. No signup means no OAuth flow for calendar or CRM access. That limits Loop to inferring state from email metadata: reply count, participant list changes, and thread activity timestamps.
Notification Timing and Throttling
Loop waits “a few weeks” before checking in. That delay is a design choice with trade-offs:
| Timing Strategy | Pros | Cons |
|---|---|---|
| Fixed delay (e.g., 14 days) | Simple to implement, predictable | Misses fast-moving intros, annoys slow ones |
| Adaptive delay based on reply velocity | Matches natural cadence | Requires more state, harder to explain |
| User-configurable delay | Flexible | Adds UI complexity, breaks “no signup” model |
| Event-driven (trigger on thread silence) | Timely | Needs continuous email polling, higher cost |
Loop appears to use a fixed delay. The landing page says “a few weeks later” without qualification. That keeps the state model simple: one scheduled task per intro, no dynamic rescheduling.
Throttling is the other half of the problem. If Loop checks in too often, it becomes spam. If it checks in too rarely, the introducer forgets the context. A reasonable heuristic:
- First check-in at 14 days
- Second check-in at 30 days if no response
- Stop after two attempts
Loop’s privacy model (no persistent user account) makes it hard to implement per-user throttling rules. The service has to decide globally or infer preferences from reply behavior.
Privacy and Access Control
Loop’s “no signup, no inbox access” constraint is a privacy feature and an architectural constraint. Without OAuth, Loop cannot:
- Read the full email thread (only messages where it was CC’d)
- Access the introducer’s calendar
- Write to the introducer’s CRM
- Authenticate the introducer’s identity
That means Loop must treat every CC as an ephemeral permission grant. The email address loop@getlooped.cc becomes the access token. If someone CC’s Loop on a sensitive intro, Loop sees it. If they forget to CC Loop on a follow-up, Loop loses visibility.
The privacy boundary is the CC line. Loop can only act on data explicitly shared with it. That’s cleaner than asking for inbox access, but it creates gaps. If the introducer wants to revoke access, they stop CC’ing Loop. There’s no admin panel to delete data because there’s no account to log into.
Conflict Resolution Across Systems
When email state conflicts with calendar state or CRM state, someone has to decide which source is authoritative. Loop sidesteps this by only using email. But if you wanted to build a more robust version, you’d face:
- Email says no reply, calendar shows a meeting: trust the calendar
- CRM says deal closed, email thread is silent: trust the CRM
- Calendar invite was sent but declined: ambiguous, needs human input
A full-featured intro tracker would need a reconciliation layer that merges signals from multiple sources and flags conflicts for manual review. Loop avoids this complexity by staying email-only, but that limits its ability to detect outcomes.
Implementation Sketch
A minimal Loop-style service needs:
# Simplified state machine for intro tracking
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
class IntroState(Enum):
PENDING = "pending"
RESPONDED = "responded"
FOLLOWED_UP = "followed_up"
CLOSED = "closed"
@dataclass
class Intro:
thread_id: str
introducer: str
party_a: str
party_b: str
created_at: datetime
state: IntroState
last_activity: datetime
follow_up_scheduled: datetime | None
def should_follow_up(intro: Intro, now: datetime) -> bool:
"""Decide if it's time to check in."""
if intro.state != IntroState.PENDING:
return False
if intro.follow_up_scheduled and now >= intro.follow_up_scheduled:
return True
# Default: follow up 14 days after last activity
return (now - intro.last_activity) >= timedelta(days=14)
def update_from_email(intro: Intro, email_metadata: dict) -> Intro:
"""Infer state changes from email replies."""
if email_metadata["reply_count"] > 0:
intro.state = IntroState.RESPONDED
intro.last_activity = email_metadata["last_reply_at"]
return intro
The real implementation needs:
- Email ingestion: SMTP server or Gmail API webhook
- Thread parsing: extract participants, timestamps, reply count
- Scheduled tasks: cron job or queue (Celery, Temporal, Inngest)
- Notification delivery: send follow-up emails via SMTP or SendGrid
- Deduplication: avoid double-sending if the introducer CC’s Loop twice
Observability Gaps
Loop has no way to know:
- If the intro led to a meeting (unless someone replies to tell it)
- If the parties moved to Slack or text
- If the introducer manually followed up without CC’ing Loop
- If the email was marked as spam
These gaps are inherent in the “no signup” model. You can’t instrument what you can’t see. The trade-off is simplicity and privacy at the cost of completeness.
A more invasive version would ask for:
- Calendar read access: detect meetings between intro parties
- CRM write access: log intro attempts and outcomes
- Inbox read access: parse the full thread, not just CC’d messages
Each permission expands observability but increases friction and privacy risk.
Technical Verdict
Use Loop’s architecture when:
- You want minimal user friction (no signup, no OAuth)
- Your workflow is email-native and users already CC stakeholders
- You can tolerate incomplete state (some intros will fall through the cracks)
- Privacy and simplicity matter more than comprehensive tracking
Avoid this pattern when:
- You need authoritative state across email, calendar, and CRM
- Users expect real-time notifications or adaptive timing
- The workflow involves more than three parties or complex branching
- You need audit logs or compliance-grade data retention
Loop’s design is a good example of constraint-driven architecture. By refusing to ask for inbox access, it forces a simpler state model and clearer privacy boundaries. The cost is observability. You can’t track what you can’t see, and you can’t see what users don’t explicitly share.
For warm intro tracking, that trade-off works. For more complex workflows (deal pipelines, multi-stage approvals, compliance tracking), you’d need richer integration and a more sophisticated state reconciliation layer.