Customer Onboarding
This example demonstrates a customer onboarding process using Upstash Workflow. The following example workflow registers a new user, sends welcome emails, and periodically checks and responds to the user's activity state.
Use Case
Our workflow will:
- Register a new user to our service
- Send them a welcome email
- Wait for a certain time
- Periodically check the user's state
- Send appropriate emails based on the user's activity
Code Example
import { serve } from "@upstash/workflow/nextjs"type InitialData = { email: string}export const { POST } = serve<InitialData>(async (context) => { const { email } = context.requestPayload await context.run("new-signup", async () => { await sendEmail("Welcome to the platform", email) }) await context.sleep("wait-for-3-days", 60 * 60 * 24 * 3) while (true) { const state = await context.run("check-user-state", async () => { return await getUserState() }) if (state === "non-active") { await context.run("send-email-non-active", async () => { await sendEmail("Email to non-active users", email) }) } else if (state === "active") { await context.run("send-email-active", async () => { await sendEmail("Send newsletter to active users", email) }) } await context.sleep("wait-for-1-month", 60 * 60 * 24 * 30) }})async function sendEmail(message: string, email: string) { // Implement email sending logic here console.log(`Sending ${message} email to ${email}`)}type UserState = "non-active" | "active"const getUserState = async (): Promise<UserState> => { // Implement user state logic here return "non-active"}from fastapi import FastAPIfrom typing import Literal, TypedDictfrom upstash_workflow.fastapi import Servefrom upstash_workflow import AsyncWorkflowContextapp = FastAPI()serve = Serve(app)UserState = Literal["non-active", "active"]class InitialData(TypedDict): email: strasync def send_email(message: str, email: str) -> None: # Implement email sending logic here print(f"Sending {message} email to {email}")async def get_user_state() -> UserState: # Implement user state logic here return "non-active"@serve.post("/customer-onboarding")async def customer_onboarding(context: AsyncWorkflowContext[InitialData]) -> None: email = context.request_payload["email"] async def _new_signup() -> None: await send_email("Welcome to the platform", email) await context.run("new-signup", _new_signup) await context.sleep("wait-for-3-days", 60 * 60 * 24 * 3) while True: async def _check_user_state() -> UserState: return await get_user_state() state: UserState = await context.run("check-user-state", _check_user_state) if state == "non-active": async def _send_email_non_active() -> None: await send_email("Email to non-active users", email) await context.run("send-email-non-active", _send_email_non_active) else: async def _send_email_active() -> None: await send_email("Send newsletter to active users", email) await context.run("send-email-active", _send_email_active) await context.sleep("wait-for-1-month", 60 * 60 * 24 * 30)Code Breakdown
1. New User Signup
We start by sending a newly signed-up user a welcome email:
await context.run("new-signup", async () => { await sendEmail("Welcome to the platform", email)})async def _new_signup() -> None: await send_email("Welcome to the platform", email)await context.run("new-signup", _new_signup)2. Initial Waiting Period
To leave time for the user to interact with our platform, we use context.sleep to pause our workflow for 3 days:
await context.sleep("wait-for-3-days", 60 * 60 * 24 * 3)await context.sleep("wait-for-3-days", 60 * 60 * 24 * 3)3. Periodic State Check
We enter an infinite loop to periodically (every month) check the user's engagement level with our platform and send appropriate emails:
while (true) { const state = await context.run("check-user-state", async () => { return await getUserState() }) if (state === "non-active") { await context.run("send-email-non-active", async () => { await sendEmail("Email to non-active users", email) }) } else if (state === "active") { await context.run("send-email-active", async () => { await sendEmail("Send newsletter to active users", email) }) } await context.sleep("wait-for-1-month", 60 * 60 * 24 * 30)}while True: async def _check_user_state() -> UserState: return await get_user_state() state: UserState = await context.run("check-user-state", _check_user_state) if state == "non-active": async def _send_email_non_active() -> None: await send_email("Email to non-active users", email) await context.run("send-email-non-active", _send_email_non_active) else: async def _send_email_active() -> None: await send_email("Send newsletter to active users", email) await context.run("send-email-active", _send_email_active) await context.sleep("wait-for-1-month", 60 * 60 * 24 * 30) Key Features
-
Non-blocking sleep: We use
context.sleepfor pausing the workflow without consuming execution time (great for optimizing serverless cost). -
Long-running task: This workflow runs indefinitely, checking and responding to a users engagement state every month.