The duplication trap

The first campaign is easy. The second campaign is where a workflow design starts showing its cost. Copying a working n8n flow feels fast until every copy needs slightly different questions, messages, appointment windows, reminder timing, or template choices.

Soon, a bug fix becomes a synchronization exercise across several almost-identical workflows. That is not reuse; it is delayed maintenance.

The boundary I wanted

I treat workflows as owners of behavior and campaigns as owners of business variation. The workflow decides how qualification progresses, how a message is routed, and when a slot is valid. The campaign decides which questions are asked, which customer-facing texts apply, and which availability rules fit that business.

business variation โ†’ campaign configuration
safety and execution behavior โ†’ workflow logic

That boundary is deliberately not absolute. A campaign should be able to choose its communication and scheduling rules. It should not be able to disable opt-out handling or make a reminder interrupt an active appointment selection.

Why this changes maintenance

The separation means a customer-facing change does not require editing production workflow logic. It also means a workflow improvement does not require manually repeating the same fix across campaign copies.

Testing becomes more understandable too. I can reason about a stable workflow contract and then validate a campaign against that contract. Neither side has to pretend it owns the other side's responsibility.

The runtime layer described in my configuration publishing approach is what makes this boundary practical during a live conversation.

What I learned

Reusable automation does not mean turning every decision into a field. The useful question is whether a change expresses a business choice or changes how the system protects and executes a conversation.

That distinction matters more than the exact table layout or JSON shape behind it.

What should happen next

The next step is formal schema validation for each campaign module. A publish action should reject an incomplete campaign before a missing field becomes a live conversation problem.