Documentation
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.
[
{ 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.
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.
// 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 customer4. A/B Tested Drip Campaign
Test different email variants at each step. The selected variant is stored on the enrollment for tracking conversion rates.
[
{ 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.
[
{ 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.
[
{ 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.
{
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.
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);