back to articles
Luka Mrkić

Luka Mrkić

Head of BD

How to Build a LangGraph Marketing Agent with Memory

How to Build a LangGraph Marketing Agent with Memory

57.3% of AI engineering teams run AI agents in production, according to LangChain’s State of Agent Engineering report (1,340 respondents, Dec 2025). Marketing teams are moving fast: a Google Cloud study of 3,466 executives found marketing is the #2 enterprise AI agent use case at 46%, behind only customer service.

The gap between “I want a marketing agent” and “I have one running” usually comes down to two problems. Most tutorials ranking today still use set_entry_point(), a deprecated v0.1 API pattern that throws LangGraphDeprecationWarning on every run in v1.0. And none of them connect marketing-relevant tools - they use weather lookups or math functions as examples, which doesn’t help you build something that researches a prospect and updates your CRM.

This tutorial builds a complete LangGraph v1.0 marketing agent: four real tools, persistent memory that survives restarts, and code blocks that run clean.

Key Takeaways

  • 57.3% of AI engineering teams run LangGraph agents in production; marketing is the #2 enterprise use case at 46% (LangChain + Google Cloud, 2025).
  • LangGraph v1.0 (stable Oct 2025) uses create_react_agent and StateGraph - the deprecated set_entry_point() pattern that most tutorials still teach was removed in v1.x.
  • Selective memory cuts agent p95 latency 91% compared to full-context retrieval, using 90% fewer tokens (Mem0 ECAI 2025).

What does a LangGraph marketing agent do that a LangChain chain can’t?

Marketing is the #2 enterprise AI agent use case at 46% of surveyed organizations, with 74% of those teams reporting AI agent ROI within the first year (Google Cloud / National Research Group, Sep 2025). Agents outperform simpler automation because of state, not model quality. A LangGraph agent remembers what it did two steps ago and routes the next action based on that context. A LangChain chain runs one prompt and stops.

The difference is concrete for marketing workflows. To do something with a chain’s response (update a CRM record, send a Slack alert, draft a follow-up email), you’d wire up separate automation steps outside the chain. A LangGraph agent keeps a state graph between those steps. It can research a prospect, then draft copy based on what it found, then log both the research and the draft to your CRM, then ping your Slack channel, all within one agent invocation.

“Stateful” is the technical word for this. The agent carries a messages list and any custom fields you define across every tool call in the run. It reads previous tool outputs before deciding what to call next. That loop is what LangGraph adds - and it’s what makes the marketing use case viable for multi-step work.

The setup looks like this: create_react_agent (the v1.0 fast path) wires an LLM to a list of tools and a memory checkpointer. StateGraph (the custom path) gives you explicit control over nodes and conditional edges. Both are in LangGraph v1.0. Both are covered below. For most marketing agents, create_react_agent is the right starting point.

For teams already using LangChain for simpler qualifier tasks, the AI lead qualification agent built on LangChain shows how to extend that foundation into the stateful multi-step workflows that LangGraph enables.


How do you set up a LangGraph v1.0 project?

LangGraph v1.0 reached stable release in October 2025, but the top-ranking tutorials still use deprecated v0.1 patterns (AgentsIndex.ai analysis, Apr 2026). The set_entry_point() method, add_conditional_edges with END as a string, and StateGraph(AgentState) with AgentState = TypedDict(...) at the module level - all deprecated. Building on them means rewriting when the warnings become errors. This build uses only v1.0 API patterns.

Step 1: Install dependencies.

pip install langgraph langchain-openai langchain-community \
            langchain-anthropic python-dotenv

LangGraph 0.2+ (the v1.0 series) ships create_react_agent in langgraph.prebuilt and SqliteSaver in langgraph.checkpoint.sqlite. Both are used below.

Step 2: Initialize the LLM and environment.

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI

load_dotenv()

llm = ChatOpenAI(
    model="gpt-4o-mini",
    api_key=os.getenv("OPENAI_API_KEY")
)

gpt-4o-mini handles tool-calling well and runs at roughly $0.002 per 1,000 tokens. Swap in gpt-4o for higher-value prospects or longer research tasks. To use Claude, replace ChatOpenAI with ChatAnthropic from langchain-anthropic.

When to use create_react_agent vs. StateGraph: create_react_agent builds a ReAct loop around your LLM and tools in about six lines. Use it when the agent decides which tool to call based on the task - which covers most marketing workflows. Use StateGraph when you need conditional branching based on tool output (for example, running a competitor analysis tool only if the prospect is in a specific industry). This tutorial uses create_react_agent for the main build and notes where StateGraph fits.

AI agents in production by company size: overall 57.3%, 10k+ employees 67%, 100-2000 employees 63%, under 100 employees 50% (LangChain State of Agent Engineering, Dec 2025)


How do you define the agent state and build marketing tools?

83.82% of marketers report increased productivity since adopting AI, with an average of 5+ hours saved per week (CoSchedule State of AI in Marketing, 1,005 professionals, Dec 2024). The tools you wire into the agent determine whether that productivity gain lands on research tasks, content drafting, CRM hygiene, or all three in a single run.

For StateGraph builds, you’ll define a TypedDict that carries marketing context across nodes:

from typing import TypedDict, Annotated
import operator
from langchain_core.messages import BaseMessage

class MarketingAgentState(TypedDict):
    messages: Annotated[list[BaseMessage], operator.add]
    campaign_context: str
    contact_id: str

For create_react_agent, you don’t define a custom state - it uses MessagesState internally. The custom TypedDict matters when you want to thread campaign-level fields (like campaign_context or contact_id) through multiple nodes explicitly.

The four tools this agent needs:

from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults

@tool
def search_prospect(query: str) -> str:
    """Search for prospect or company news online."""
    search = TavilySearchResults(max_results=3)
    results = search.invoke(query)
    return str(results)

@tool
def update_crm(contact_id: str, note: str) -> str:
    """Log a note or update a field on a CRM contact record."""
    # Replace with your CRM API call (HubSpot, Salesforce, Pipedrive)
    return f"CRM updated for contact {contact_id}: {note}"

@tool
def draft_content(content_type: str, context: str) -> str:
    """Generate a short-form marketing asset: email_intro, social_post, or ad_headline."""
    valid_types = ["email_intro", "social_post", "ad_headline"]
    if content_type not in valid_types:
        return f"Invalid content_type. Use one of: {valid_types}"
    return f"[Draft {content_type} based on context]: {context}"

@tool
def send_slack_alert(channel: str, message: str) -> str:
    """Send a notification to a Slack channel."""
    # Replace with your Slack Bolt or webhook integration
    return f"Alert sent to #{channel}: {message}"

tools = [search_prospect, update_crm, draft_content, send_slack_alert]

The @tool decorator reads the docstring to generate the tool description that the LLM uses to decide when to call each function. Write the docstring as a precise action statement, not a vague label. “Search for prospect or company news online” tells the model when to reach for the tool; “Search tool” doesn’t.

In practice, draft_content produces the most variable outputs across runs. The fix isn’t a better prompt - it’s stricter validation in the tool body. Adding an explicit content_type whitelist (as shown above) and returning a clear error string when the model passes an invalid type reduces hallucinated formats from appearing in CRM notes. The model reads the error and self-corrects on the retry without any intervention.

For managing the API keys that search_prospect and update_crm call, the patterns in the marketing API credential management guide apply directly here - environment variables over hardcoded keys, one key per integration.


How do you add persistent memory to a LangGraph marketing agent?

Selective memory strategies cut agent p95 latency by 91% compared to full-context retrieval, from 17.12 seconds to 1.44 seconds, using 90% fewer tokens (Mem0 ECAI 2025 research, LOCOMO benchmark, 10 memory approaches tested). The choice of memory backend has direct consequences on both performance and reliability across sessions.

LangGraph offers two main checkpointers:

  • MemorySaver (from langgraph.checkpoint.memory): stores state in RAM. Fast to set up, zero dependencies. State is gone when the process exits. Fine for development; wrong for production marketing agents that track campaign context across sessions.
  • SqliteSaver (from langgraph.checkpoint.sqlite): writes state to a local SQLite database file. Survives process restarts, container redeploys, and server reboots. The correct default for any marketing agent that needs to remember what campaigns have been sent, which contacts have been researched, or what messaging has been approved.
from langgraph.checkpoint.sqlite import SqliteSaver

# Persistent checkpointer — state written to marketing_agent.db
memory = SqliteSaver.from_conn_string("marketing_agent.db")

# One thread_id per campaign, prospect, or workflow session
config = {"configurable": {"thread_id": "campaign-q2-2026"}}

The thread_id is the memory key. Use one thread per campaign to accumulate context about that outreach push, or one per high-value prospect to carry research, prior touchpoints, and approved messaging across runs over days or weeks. A multi-user marketing interface needs its own thread per user to keep each person’s context isolated.

Agent memory strategy p95 latency: full-context 17.12s vs selective memory 1.44s - 91% reduction with 90% fewer tokens (Mem0 ECAI 2025, LOCOMO benchmark)

Most teams default to MemorySaver because it’s the first checkpointer shown in the official docs. They hit the wall when restarting the agent or redeploying the container wipes all campaign context. The correct default for marketing agents is SqliteSaver from day one, not as an upgrade. Campaign context, including audience segments, prior outreach, and approved copy, must survive process restarts. Starting with MemorySaver means migrating state later, which introduces data-loss risk if the old in-memory thread IDs don’t map cleanly to the new SQLite records. Use SqliteSaver from the first commit.


How do you build the graph and run the marketing agent?

AppFolio’s LangGraph-powered Realm-X agent saved property managers 10+ hours per week after deployment (LangChain Blog, 2024). The productivity gain came from the graph structure: a well-designed agent loop handles research, drafting, and notification in sequence without human handoffs between steps. The same structure applies to marketing workflows.

Build the agent with create_react_agent:

from langgraph.prebuilt import create_react_agent

# Assemble the agent — v1.0 API, no deprecated patterns
agent = create_react_agent(
    model=llm,
    tools=tools,
    checkpointer=memory
)

That’s the full build. The ReAct loop (Reasoning + Acting) is handled internally: the model decides which tool to call, calls it, reads the output, then decides whether to call another tool or return a final answer. The checkpointer=memory line wires in SqliteSaver so state persists between runs.

Run it with a real marketing task:

task = """
Research Acme Corp (acmecorp.com). Find their most recent product launch or
company news. Then:
1. Draft a 20-word personalized cold email intro referencing that news.
2. Update the CRM for contact_id='acme-ceo-001' with a summary of what you found.
3. Send a Slack alert to #outbound-team with the draft intro.
"""

response = agent.invoke(
    {"messages": [{"role": "user", "content": task}]},
    config=config
)

print(response["messages"][-1].content)

The agent will call search_prospect, read the results, call draft_content with the research as context, call update_crm with the summary, then call send_slack_alert. Each tool call and its output is logged in the SQLite file under the thread_id in config.

Teams using CrewAI for parallel research tasks can compare the orchestration approaches: the CrewAI content research agent shows how multi-agent role assignment handles research at scale, while LangGraph’s single-agent loop with tools handles sequential decision-making more cleanly for CRM workflows.


How do you test and scale a LangGraph marketing agent?

Quality is the top production barrier for 32–33% of AI agent teams, ahead of cost, latency, and integration complexity (LangChain State of Agent Engineering, Dec 2025). Agentic tasks complete 66.8% faster on average than manual equivalents (First Page Sage Agentic AI Statistics 2026, 487-user evaluation, Apr 2026), but only when the agent is reliable enough to run unsupervised.

Enable LangSmith tracing first. Set these environment variables before your first real campaign run:

export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=your_langsmith_api_key
export LANGCHAIN_PROJECT=marketing-agent-prod

LangSmith logs every tool call, every input/output, and every state transition. When the agent calls update_crm with a malformed contact_id or loops on a failed search_prospect result, you’ll see it immediately in the trace rather than discovering it from a CRM data audit.

Before going live, run 10 diverse marketing tasks manually, score each final output 1–5 on relevance and accuracy, and document which task types produce scores below 3. Those are your prompt improvement targets. The most common failure mode in marketing agents isn’t the LLM - it’s the tool docstring. If the model consistently reaches for the wrong tool, rewrite the docstring to be more specific about when the tool applies.

Scaling from one mailbox to many: Once the agent runs reliably on one contact, scale by increasing thread_id specificity. Use {campaign_id}-{contact_id} as the thread ID format so each contact’s research, draft, and CRM note stay isolated. When scaling to 100+ contacts per day, move from SqliteSaver to PostgresSaver from langgraph-checkpoint-postgres - it handles concurrent writes that SQLite blocks.

AI agent production adoption 2024 vs 2025: overall 51% to 57.3%, large enterprise 67% in 2025 (LangChain State of AI Agents 2024 + State of Agent Engineering Dec 2025)

For teams managing multiple agent workflows across revenue functions, the governance and approval patterns covered in the AI workflow automation guide for revenue teams apply directly to scaling agent pipelines across campaigns, regions, and personas.


If you’re looking to integrate AI into your marketing workflows, get in touch with us and we’ll map out where automation adds the most value for your team.


FAQ

What is LangGraph used for in marketing?

LangGraph handles multi-step marketing workflows that require persistent state: researching prospects, drafting personalized copy, updating CRM records, and routing Slack alerts within a single agent run. Marketing is the #2 enterprise AI agent use case at 46% of organizations surveyed (Google Cloud / National Research Group, Sep 2025).

What is the difference between LangGraph and LangChain for agents?

LangChain chains run once and return a result. LangGraph agents maintain a state graph across multiple tool calls and loop until the task is complete, making LangGraph the right choice for multi-step marketing tasks. LangChain provides the LLM and tool primitives; LangGraph orchestrates how they execute over time with persistent memory.

How do I add persistent memory to a LangGraph agent?

Use SqliteSaver from langgraph.checkpoint.sqlite for production and MemorySaver from langgraph.checkpoint.memory for local development. Pass the checkpointer to create_react_agent() or graph.compile(), then include a thread_id in the config dict on every invocation. SqliteSaver writes state to a .db file that survives process restarts; MemorySaver stores state in RAM only.

Does LangGraph work with Claude instead of GPT?

Yes. Replace ChatOpenAI with ChatAnthropic from the langchain-anthropic package and set model='claude-sonnet-4-6' or 'claude-opus-4-6'. The tool-calling interface, StateGraph, and SqliteSaver checkpointer work identically. Claude’s extended context window suits marketing agents that accumulate multi-turn campaign context across long sessions.

Is LangGraph v1.0 production-ready in 2025?

LangGraph v1.0 reached stable release in October 2025, and 57.3% of AI engineering teams already run these agents in production (LangChain State of Agent Engineering, Dec 2025). The main production consideration is observability: enable LangSmith tracing from day one to catch tool failures and runaway loops before they affect your CRM or outbound campaigns.


Run the stack, then refine the tools

The agent in this tutorial has four components: the LLM, the tool suite (search, CRM write, content draft, Slack notify), the SqliteSaver memory backend, and the create_react_agent graph that connects them. Each layer needs to be correct before the next one matters:

  • Weak tool docstrings cause the model to reach for the wrong tool more than weak prompts do
  • MemorySaver in production means campaign context disappears on every deploy
  • Missing LangSmith tracing means quality failures surface in your CRM, not in your logs

A well-configured agent with specific tool docstrings, persistent SQLite memory, and LangSmith tracing enabled should handle a 10-contact research and outreach sequence without manual intervention. That’s the right baseline to validate before scaling.

Once the agent is running, two natural extensions follow: an AI lead qualification agent built on LangChain to score and route the inbound replies the marketing agent generates, and the n8n + Perplexity competitive intelligence agent to feed fresh competitor signals into the search_prospect tool context before each outreach run.