Venue Lifecycle โ Day by Day
Sequence of events for a single venue as it moves through the follow-up system.
One venue, one thread, from label-applied to resolved/escalated. Each "Day N" represents a single cron firing at 12pm Sydney time.
๐บ Lifecycle at a glance
The happy-path and each branch summarised in one strip. Most venues terminate on Day 1 or 2 (reply arrives). Worst case is Day 3 โ escalated.
Day 0
Label applied
Team flags thread
pending intake
Day 1
Intake + Day 1 draft
cycle_day 0 โ 1 โ 2
active
Day 2
Day 2 draft (if no reply)
cycle_day 2 โ 3
active
Day 3
Escalate (if no reply)
status โ escalated
escalated
Any day
Reply detected
status โ resolved
resolved
Day 0 โ The inquiry is sent
DAY0
Human sends inquiry
Alec/Lauren/Chelsea sends a new venue inquiry email.
From their Gmail, they compose an email to the venue (e.g. events@harbourview.com.au) asking about availability and pricing for a couple's wedding.
โ
Human applies label
They add the Start-FollowUp label to the sent thread.
This is the only manual action the team takes. Applying the label is the signal: "track this thread, follow up automatically until we get a reply."
System state at end of Day 0
| Gmail thread | 1 outbound message, labelled Start-FollowUp |
| Tracking sheet | no row yet |
| Next trigger | tomorrow 12pm Sydney (next cron firing) |
Day 1 โ First cron firing
DAY1
Cron 12pm Sydney
Cloudflare fires scheduled on the venue-agent Worker.
Worker boots cold (~150ms). Claude client initialised. Google access token refreshed from stored refresh token.
โ
Agent turn 1 โ intake
Agent calls intake_new_threads.
Gmail API: search threads with label Start-FollowUp. Finds this thread. Checks Tracking sheet โ not there yet. Reads thread metadata (subject, From, To, Date). Appends a new row.
โ intake_new_threads() returns { intaked: [{ thread_id, venue_name, venue_email }] }
โ
Sheets row appended
New row at the bottom of Tracking tab.
| A thread_id | 19d8ed2b97ce5401 |
| B venue_name | Wedding Inquiry: Harbour View Estate... |
| C sender_email | alec@easyweddings.com.au |
| D cycle_day | 1 |
| E status | active |
| F last_action_date | 2026-04-19 |
| H created_date | 2026-04-19 |
| J venue_email | events@harbourview.com.au |
โ
Gmail label removed
The Start-FollowUp label is stripped from the thread.
Prevents re-intake tomorrow. Thread stays in the mailbox with all its normal labels (SENT, etc.).
โ
Agent turn 2 โ fetch actives
Agent calls get_active_venues + check_thread_replies for this venue.
Sheet query returns the row (plus any other actives). Gmail thread fetch returns 1 message โ from alec@easyweddings.com.au. No external sender present.
โ
Agent turn 3 โ decide + act
No reply + cycle_day=1 โ draft Day 1 template.
Claude personalises the opening with the couple's names and venue, writes the body, calls create_followup_draft + update_venue_row.
โ draft created in thread
โ cycle_day bumped 1 โ 2
โ last_action_date = 2026-04-19
โ
Gmail draft lives in Drafts folder
Inside the original thread โ subject prefixed "Re: ".
Nothing has been sent. Alec opens Drafts when he's ready, reviews, hits Send. The draft is threaded correctly so it shows up in the conversation view.
โ
Slack run summary
Agent posts one combined message to #make_team.
"1 intaked / 1 checked / 0 replies / 1 draft / 0 escalated". Full sign-off for the run.
End of Day 1 โ waiting for reply
Sheet row is now cycle_day=2, status=active. Next cron tomorrow re-checks the thread and decides based on whether a reply arrived in the intervening 24 hours.
Day 2 โ Second cron firing
DAY2
Cron 12pm Sydney
Worker wakes again.
โ
Agent intake + fetch
intake_new_threads (maybe picks up new labelled threads today, unrelated) โ get_active_venues โ check_thread_replies for our venue.
โ
Branch: did the venue reply?
โ
YES โ reply in thread
any message.from โ internal addresses
update_venue_row({
status: "resolved",
resolved_date: today
})
โ NO draft created
โ Slack: "1 reply found"
โ NO โ still waiting
only internal senders in thread, cycle_day=2
create_followup_draft(Day 2 template, personalised)
update_venue_row({
cycle_day: 3,
last_action_date: today
})
โ Day 2 draft in Drafts folder
โ Slack: "1 draft created"
Sheet state at end of Day 2 (if no reply)
| D cycle_day | 3 |
| E status | active |
| F last_action_date | 2026-04-20 |
Day 3 โ Third cron firing
DAY3
Agent check
cycle_day=3 โ this is the decision point.
Agent fetches thread. If a reply arrived overnight, the reply branch fires (resolve). If not, cycle_day=3 means no more drafts โ escalate.
โ
Branch: last chance
โ
Reply arrived
any external sender in thread
update_venue_row({
status: "resolved",
resolved_date: today
})
โ NO draft created
โ Slack: "1 reply found"
๐จ Still no reply โ escalate
only internal senders, cycle_day=3
update_venue_row({
status: "escalated"
})
โ NO draft created (venue has had 2 follow-ups already)
โ included in Slack "escalations" section
โ team reviews manually
Terminal state โ escalated
The venue had an initial inquiry + Day 1 + Day 2 follow-up โ 3 outreach attempts in total โ with no response. Status flips to escalated. The agent will ignore this row on future runs (get_active_venues filters by status='active' only). A human decides the next step: manual outreach, move to a different contact, or archive.
Day 4 and beyond
DAY4+
Agent ignores the row
Because status is resolved or escalated, not active.
The agent only operates on rows where status = "active". Resolved and escalated rows are invisible to the cycle โ they sit in the sheet as a historical record but cost nothing in tokens or API calls going forward.
โ
Human can re-open
If you want the agent to chase again, set status back to active + cycle_day to the next stage manually.
Example: a venue replies 3 weeks after being escalated โ flip to active, cycle_day=1, and the agent resumes drafting on the next cron.
Edge cases & special flows
Reply arrives mid-cycle
Suppose we're on Day 2 and the venue replies at 10am Sydney. The agent runs at 12pm. During check_thread_replies, it sees the new message from a non-internal sender and takes the resolve branch. No Day 2 draft is created. Sheet flips to resolved.
Multiple threads labelled on the same day
Intake pulls every labelled thread in one batch. If the team labels 10 threads in the morning, the 12pm run intakes all 10 and starts the Day 1 cycle for each. Each gets its own row, its own draft.
Label applied to a thread that's already being tracked
Intake dedups by thread_id. If a thread's id is already in the sheet, it's skipped (but the label is still removed). Prevents duplicates if someone re-labels by accident.
Non-inquiry threads get labelled
If a team member labels a weird thread (e.g. internal email, promotional content), it still gets intaked. The agent will try to draft follow-ups. Mitigation: label removal + human review of Drafts before sending means bad drafts never reach the venue.
Venue reply is from a different address than the one we emailed
Handled. Reply detection checks whether ANY message in the thread is from an address outside {alec@, lauren@, travel@easyweddings.com.au}. Personal Gmail, Outlook, new staff โ all trigger resolve.
Venue uses @easyweddings.com.au to reply (unlikely)
Would be missed โ agent would interpret it as "still no reply" and keep drafting. Low risk given it's an internal domain match. Fix if it ever happens: add their specific reply address to a user-managed exclusion list.
Timing summary
Each venue has at most 3 agent interactions before termination:
- First 24 hours โ Day 1 draft created
- 48 hours โ Day 2 draft (if still no reply)
- 72 hours โ escalation (if still no reply)
Fastest resolution path: venue replies same day โ next cron detects + resolves โ total elapsed time = original inquiry + up to 24 hours.
Slowest path (pre-escalation): 72 hours. After that, human takes over.
What a first production run will actually look like
Whenever the team starts labelling threads with Start-FollowUp, the next 12pm cron will:
- Intake every labelled thread into the sheet (could be 10, could be 100 โ no hard cap beyond Gmail's 50-per-page limit, which the intake loop iterates past)
- For each new venue, run the Day 1 logic โ check thread, draft, update sheet
- Post one Slack summary at the end
Cost: ~$0.02 per new venue on Day 1, ~$0.015 per existing venue on subsequent days. 50 venues/day = ~$1/day = ~$30/month.
Generated 2026-04-19 ยท venue-agent v0.2.0 (intake added)