LangGraph pushed LangChain into a direction many people had been asking for: explicit state, explicit transitions, deterministic control flow. It's a better fit for production agents than a freeform ReAct loop. It's also a perfect fit for modeling email-driven workflows — because "wait for email" is literally a state.

Email workflows as graphs

Consider the most common agent task: sign up for a service, verify the email, complete the flow. Modeled as a graph:

  • create_inboxsubmit_signupwait_for_otpsubmit_otpdone

Each node is a concrete step with inputs and outputs. The edges are deterministic. If OTP times out, transition to retry or fail. This is exactly what LangGraph's StateGraph was built for.

Wiring Lumbox as graph nodes

from langgraph.graph import StateGraph, END
from typing import TypedDict
from lumbox import Lumbox

lumbox = Lumbox(api_key=os.environ["LUMBOX_API_KEY"])

class AgentState(TypedDict):
    inbox_id: str
    address: str
    otp: str | None
    target_url: str

def create_inbox_node(state: AgentState) -> AgentState:
    inbox = lumbox.inboxes.create(display_name="signup")
    return {**state, "inbox_id": inbox.id, "address": inbox.address}

def submit_signup_node(state: AgentState) -> AgentState:
    # browser automation fills form with state["address"]
    return state

def wait_for_otp_node(state: AgentState) -> AgentState:
    result = lumbox.inboxes.wait_for_otp(state["inbox_id"], timeout=120)
    return {**state, "otp": result.otp}

def submit_otp_node(state: AgentState) -> AgentState:
    # browser automation enters state["otp"]
    return state

graph = StateGraph(AgentState)
graph.add_node("create_inbox", create_inbox_node)
graph.add_node("submit_signup", submit_signup_node)
graph.add_node("wait_for_otp", wait_for_otp_node)
graph.add_node("submit_otp", submit_otp_node)
graph.set_entry_point("create_inbox")
graph.add_edge("create_inbox", "submit_signup")
graph.add_edge("submit_signup", "wait_for_otp")
graph.add_edge("wait_for_otp", "submit_otp")
graph.add_edge("submit_otp", END)
app = graph.compile()

Durability

LangGraph's checkpointing plays perfectly with Lumbox — the wait_for_otp node can run, resume, or replay. If your process crashes mid-flow, the state is persisted and picks up where it left off. Lumbox inboxes persist server-side, so the OTP is still retrievable when you come back. lumbox.co.