Documentation

DocsGuidesWorkflow Patterns

Workflow Patterns

Common patterns for building email automations with OutreachAgent workflows.

1. Outbound Drip Campaign

Send a sequence of emails with delays between each step. Add exit criteria so contacts automatically leave the sequence if they reply, bounce, or unsubscribe.

Workflow Nodes
[
  { id: "n1", type: "send_email", label: "Cold intro", templateId: "tmpl_intro", nextNodeId: "n2" },
  { id: "n2", type: "delay", label: "Wait 3 days", delayAmount: 3, delayUnit: "days", nextNodeId: "n3" },
  { id: "n3", type: "send_email", label: "Follow up", templateId: "tmpl_followup", nextNodeId: "n4" },
  { id: "n4", type: "delay", label: "Wait 5 days", delayAmount: 5, delayUnit: "days", nextNodeId: "n5" },
  { id: "n5", type: "send_email", label: "Breakup email", templateId: "tmpl_breakup", nextNodeId: "n6" },
  { id: "n6", type: "exit", label: "Done", nextNodeId: null }
]

// With exit criteria — contact exits automatically on reply or bounce:
exitCriteria: [{ trigger: "reply" }, { trigger: "bounce" }, { trigger: "unsubscribe" }]

2. Inbound Triage with Branching

Process inbound emails and route to different templates based on content classification.

Webhook Handler
app.post("/webhooks/outreachagent", async (req, res) => {
  const { event, data } = req.body;

  if (event === "message.received") {
    // Classify the inbound email
    const classification = await classifyEmail(data.subject, data.preview);

    if (classification === "support") {
      await client.sendMessage({
        inboxId: data.inboxId,
        subject: "Re: " + data.subject,
        body: "Thanks for reaching out! A team member will review your message and get back to you shortly.",
        from: data.to[0],
        to: [data.from]
      });
    } else if (classification === "sales") {
      // Enroll in sales drip
      const contact = await client.createContact({ email: data.from, fullName: "Lead" });
      await client.createEnrollment({ workflowId: "wf_sales_drip", contactId: contact.id });
    }
  }

  res.status(200).send("ok");
});

3. Human-in-the-Loop Approval

Send to a human team via cc/bcc for review. Your runtime drafts the email, a human approves, and the workflow continues.

Pattern
// Step 1: Your runtime drafts and sends with a human cc'd
await client.sendMessage({
  inboxId: "inb_agent",
  subject: "Draft: Proposal for Acme Corp",
  body: "Hi team,\n\nPlease review the attached proposal draft for Acme Corp before it goes out. I've included the pricing breakdown and timeline we discussed.\n\nLet me know if any changes are needed.",
  from: "agent@yourdomain.com",
  to: ["manager@yourteam.com"]  // Human reviewer
});

// Step 2: Webhook receives human reply with approval
// Step 3: Agent sends the final version to the customer

4. A/B Tested Drip Campaign

Test different email variants at each step. The selected variant is stored on the enrollment for tracking conversion rates.

Workflow Nodes
[
  { id: "n1", type: "ab_test", label: "Test intro email", variants: [
    { id: "friendly", templateId: "tmpl_friendly_intro", weight: 50 },
    { id: "formal", templateId: "tmpl_formal_intro", weight: 50 }
  ], nextNodeId: "n2" },
  { id: "n2", type: "delay", label: "Wait 3 days", delayAmount: 3, delayUnit: "days", nextNodeId: "n3" },
  { id: "n3", type: "branch", label: "Check engagement", branches: [
    { condition: { field: "engagement.opened", operator: "is_true" }, nextNodeId: "n4" }
  ], nextNodeId: "n5" },
  { id: "n4", type: "send_email", label: "Follow up openers", templateId: "tmpl_followup", nextNodeId: "n6" },
  { id: "n5", type: "send_email", label: "Re-engage", templateId: "tmpl_reengage", nextNodeId: "n6" },
  { id: "n6", type: "exit", label: "Done", nextNodeId: null }
]

5. Webhook Response Branching

Post to an external API and branch based on the response status or body fields.

Workflow Nodes
[
  { id: "n1", type: "send_webhook", label: "Check CRM status",
    webhookUrl: "https://crm.example.com/api/lead-score",
    webhookBody: "{\"email\": \"{{contact.email}}\"}",
    webhookResponseBranches: [
      { condition: { field: "webhook_response.body.score", operator: "equals", value: "hot" }, nextNodeId: "n2_hot" },
      { condition: { field: "webhook_response.statusCode", operator: "equals", value: "404" }, nextNodeId: "n2_unknown" }
    ],
    nextNodeId: "n2_default" },
  { id: "n2_hot", type: "send_email", label: "Send hot lead template", templateId: "tmpl_hot", nextNodeId: "n3" },
  { id: "n2_unknown", type: "exit", label: "Unknown lead, exit", nextNodeId: null },
  { id: "n2_default", type: "send_email", label: "Send nurture", templateId: "tmpl_nurture", nextNodeId: "n3" },
  { id: "n3", type: "exit", label: "Done", nextNodeId: null }
]

6. Goto Loop with Safety Guard

Use a goto node to loop back and retry a step up to a maximum number of iterations.

Workflow Nodes
[
  { id: "n1", type: "send_email", label: "Send outreach", templateId: "tmpl_1", nextNodeId: "n2" },
  { id: "n2", type: "delay", label: "Wait 5 days", delayAmount: 5, delayUnit: "days", nextNodeId: "n3" },
  { id: "n3", type: "branch", label: "Got reply?", branches: [
    { condition: { field: "engagement.replied", operator: "is_true" }, nextNodeId: "n5" }
  ], nextNodeId: "n4" },
  { id: "n4", type: "goto", label: "Loop back", targetNodeId: "n1", maxIterations: 3, nextNodeId: "n5" },
  { id: "n5", type: "exit", label: "Done", nextNodeId: null }
]

7. Scheduled Enrollment with Contact Filter

Automatically enroll contacts matching a segment on a recurring schedule.

Workflow with Scheduled Trigger
{
  name: "Weekly Re-engagement",
  trigger: "scheduled",
  schedule: {
    cronExpression: "0 9 * * 1",          // every Monday at 9am
    contactFilter: { segmentId: "seg_inactive" }
  },
  nodes: [
    { id: "n1", type: "send_email", label: "Re-engage", templateId: "tmpl_reengage", nextNodeId: "n2" },
    { id: "n2", type: "exit", label: "Done", nextNodeId: null }
  ]
}

8. Deliverability Monitoring

Set up automated monitoring to pause campaigns when deliverability degrades.

Monitoring Script
async function checkDeliverability() {
  const metrics = await client.getMetricsSummary();

  if (metrics.bounceRate > 0.02) {
    console.error("Bounce rate critical:", metrics.bounceRate);
    // Pause all active workflows
    const workflows = await client.listWorkflows();
    for (const wf of workflows.items) {
      if (wf.status === "active") {
        await client.pauseWorkflow(wf.id);
      }
    }
  }
}

// Run every 15 minutes
setInterval(checkDeliverability, 15 * 60 * 1000);