← All use casesUSE CASE

Onboarding Sequence that Skips the Activated

Runs a per-user onboarding email sequence — each touch schedules the next, and users who activate simply stop hearing from it.

schedulememory.forUsercustom-tools

model: claude-sonnet · budget: $3/day · agent.js: ~96 lines

▶ Demo video — coming soon
75 seconds, deploy to running agent
VIEW SOURCE ON GITHUB ↗agent.js ↗

DEPLOY IT

git clone https://github.com/anupsinghinfra/oncell-cookbook && cd oncell-cookbook
cp .env.example .env   # add your ONCELL_API_KEY
npm run deploy onboarding-emailer

# invoke it
curl -X POST https://api.oncell.ai/api/v1/agents/onboarding-emailer/signup \
  -H "Authorization: Bearer $ONCELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u_smoke_1","email":"smoke@test.example","name":"Smokey"}'

You know the email. You'd been using the product daily for a week, and then: "Ready to take your first step? 🚀" Nothing says we aren't paying attention like onboarding mail sent to someone already onboarded — and it happens because most onboarding automation is a broadcast schedule wearing a personalization mask. The "sequence" is a campaign that fires day-2 mail at the day-2 cohort, and the activation check is a nightly sync that missed you.

Doing it actually per-user means every signup gets an independent timeline: their own next-touch timer, their own position in the sequence, their own stop condition. A thousand users, a thousand tiny state machines with day-scale timers. That's precisely the shape of system — long-lived, mostly idle, individually stateful — that traditional infrastructure makes miserable, which is why everyone rents a campaign engine and accepts the rocket-ship email.

onboarding-emailer builds the real thing in one file.

A shelf per user

The per-user state costs one line of scoping, visible in the zero-LLM activated task in agent.js:

const profile = agent.memory.forUser(userId); // keys become user:<id>:*
await profile.set("activated", true);

memory.forUser prefixes every key with user:<id>: transparently — no collisions, no WHERE clause, no schema. The identity declares the whole per-user shelf: profile, step (1–4), activated. And notice the handler has no agent.llm call at all: your product's activation webhook flips one durable key for zero tokens, in milliseconds.

Each touch books the next — per user

The sequence itself is four steps of identity — welcome, best feature, customer story, check-in — with the gaps written into the wake procedure:

"...send the next step email with send_email, write the new step, and call schedule with note set to touch <user_id> and in set to the gap before the following step - 2 days after step 1, 3 days after step 2, 4 days after step 3, nothing after step 4."

Every user's timeline is a chain of durable wake intents carrying their own id: touch u_1042 fires in two days regardless of deploys, crashes, or how many other users signed up since. There is no cohort query, no campaign run at midnight — just each user's next touch, booked when their last one went out.

The stop conditions are checked at wake time, not at send-schedule time:

"If activated is true or step is 4 or the profile is missing, answer that the sequence is over and send nothing."

So the race that produces the rocket-ship email can't happen. User activates on day 3? The day-5 wake still fires, reads activated: true, and stands down silently. Coordination through state — the same pattern as this cookbook's invoice-chaser — with no timers to cancel and no sync to miss. Step 4's rule ("This is the last email - the sequence ends here no matter what") is the anti-drip-hell clause: the sequence is finite by construction.

send_email is the single stub; point it at SES or Postmark. Budgets scale sanely: signup runs cap at { maxSteps: 10, maxCost: 0.25 }, and the identity's perDayCents: 300 handles hundreds of signups a day.

Deploy it

git clone https://github.com/oncell/oncell-cookbook && cd oncell-cookbook
cp .env.example .env   # add your ONCELL_API_KEY
npm run deploy onboarding-emailer

Wire two webhooks:

# on signup
curl -X POST https://api.oncell.ai/api/v1/agents/onboarding-emailer/signup \
  -H "Authorization: Bearer $ONCELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "u_1042", "email": "sam@newco.example", "name": "Sam"}'

# on activation
curl -X POST .../onboarding-emailer/activated -d '{"user_id": "u_1042"}'

progress returns any user's { step, activated } for zero tokens whenever support asks "what has this user been sent?"

What you didn't have to build

A campaign engine and its segment queries. A per-user timer table with a scheduler polling it. An activation sync job and the race conditions it loses. Sequence-position tracking. A suppression list — suppression is just a key each wake reads. The runtime holds a thousand independent timelines at $0 while they wait.

You wrote four emails and their gaps. Every user gets their own clock, and the clock checks who they've become before it rings.