Adobe Campaign Classic Workflow Best Practices: Variables, Scheduling, and Error Handling
Adobe Campaign workflow best practices from real builds: naming, variables, scheduling, purging, error handling and supervision.
If you have built your first Campaign Classic workflow, you know any workflow can be made to run once. The hard part is a set of workflows that still runs a year later, after the person who built it has left, the data has doubled and three people have edited it. Most of the Adobe Campaign workflow best practices I follow came from inheriting instances where that went wrong.
Here I write about the Adobe Campaign workflow best practices I use on Campaign Classic v7: naming and folders, variables, scheduling, error handling, logging and housekeeping. Adobe’s own workflow best practices page covers much of the same ground, and where my opinion differs from the general advice, I say why.
Workflow best practices for names and folders
Names are documentation that nobody has to remember to write.
Workflow labels. Adobe suggests a numbered label when several workflows form one process, and I use the same pattern on every instance:
001 - Import - Recipients from CRM
002 - Import - Transactions
010 - Export - Delivery logs to BI
100 - Newsletter - Weekly targeting
The number sorts the list in running order. The middle part says what kind of job it is. The end says what it does. Fill the description field too, with what the workflow does, who owns it and what depends on it.
Activity names. Every activity has an internal name in its Advanced tab, generated as query, query1, query11. Rename them as you build, to something like querySubscribers. The name appears in logs and in the work table names, and renaming later can break references in scripts.
Folders. Keep business workflows in dedicated folders, not in the root of Targeting workflows. Platform-wide jobs such as purges can go in a subfolder of the technical workflows folder. Folder permissions then control who can open and change them.
Workflow best practices for starting and ending branches
Three rules I never break:
- Start with Start, Scheduler or External signal. A workflow that starts mid-branch is hard to read and harder to restart.
- One Scheduler per branch. Chaining schedulers multiplies the tasks they generate. I have seen this turn one nightly job into a flood of executions.
- End every branch with End. The start and end documentation notes that End lets Campaign free temporary space used by the workflow.
Also, avoid copying activities between workflows. Adobe warns that settings attached to activities such as deliveries and schedulers can conflict. Copying within one workflow is fine.
Variables: pass them deliberately
Campaign Classic gives you three places to store values, and choosing the right one avoids most variable bugs. I use all three in my JavaScript activity guide.
varsbelong to the current transition and travel with the data from one activity to the next. Activities set some automatically, such asvars.recCountandvars.tableName.task.varsbelong to the current activity’s task only.instance.varsbelong to the whole workflow run and can be read from any activity.
Each holds a string, a number or a date. I use instance.vars for anything set once and read in several places, like a cut-off date or a run label, and vars for anything that genuinely belongs to a population, like a file name.
To use an instance variable in a query filter, the JavaScript and templates documentation gives the syntax $(instance/vars/@xxx). For dates, $date(instance/vars/@xxx) is used. My usual pattern is to set the values in one JavaScript code activity at the start of the workflow, so all the logic for “which day are we processing” lives in one place:
// Activity: jsSetRunContext
var since = new Date();
since.setDate(since.getDate() - 7);
instance.vars.since = since;
instance.vars.runLabel = "Weekly newsletter " + formatDate(new Date(), "%4Y-%2M-%2D");
logInfo("Run context: since " + since + ", label " + instance.vars.runLabel);
Then the Query condition reads @created >= $date(instance/vars/@since), and the delivery label can use the run label. If the business rule changes from seven days to ten, there is one line to change.
What I avoid is setting the same variable in several activities. When two branches both write instance.vars.status, the value depends on which finished last, and nobody can predict it.
Adobe Campaign workflow best practices for scheduling and load
The workflow server runs everything on the instance, so one team’s schedule affects everyone else. My rules:
- Spread start times. Do not schedule every job at midnight. Stagger them.
- Load data at night. Imports and heavy aggregates go in the quiet hours.
- Split long workflows. A workflow that runs for hours is hard to restart and blocks resources. Two or three shorter ones connected by signals are easier to manage.
- Keep the number of concurrent workflows reasonable. Adobe’s guidance recommends avoiding more than 20 running at once.
When workflow B depends on workflow A, I do not schedule B “an hour later and hope”. I end A with a JavaScript activity that posts an event to an External signal activity in B, so B starts when A has actually finished:
xtk.workflow.PostEvent("newsletterWeekly", "signalDataReady", "",
<variables since={instance.vars.since}/>, false);
The first argument is the internal name of workflow B, the second is the name of its External signal activity. The variables arrive in B’s transition. Document the expected sender in the signal activity’s Comment field, as Adobe suggests, because months later nobody knows where a signal comes from.
Error handling that someone notices
The workflow execution documentation explains the options, and the choices are simple once you know them. When something still breaks, my workflow debugging guide shows how I trace it.
In the workflow properties, error management offers Stop the process, which pauses the workflow when a task fails, and Ignore, which marks the task as failed and carries on, with an optional limit on consecutive errors. I use Stop the process for anything that changes data or sends messages. A half-finished import that keeps running is worse than one that stops. I use Ignore only for recurring jobs where the next run will naturally retry, such as a file collector checking every 15 minutes.
Then set supervisors. Workflows in error notify the operators in the supervisor group by email, provided they have an email address. The default group is Workflow supervisors. I create one per team, so the import team does not get the newsletter team’s errors and start ignoring all of them.
For file-related activities, the Process errors option adds an error transition so you can handle file system problems in the workflow, for example by sending an alert, instead of pausing. It does not catch configuration errors.
Logging without filling the database
logInfo() is the quickest debugging tool, and also an easy way to fill the log table. I log once per run with a summary, never inside a loop over records.
Two properties are useful in development and harmful if left on in production:
- Keep the result of interim populations between two executions keeps work tables for inspection. Adobe says never to use it in production, and I agree without reservation.
- Log SQL queries in the journal shows the SQL each targeting activity sends. Turn it on to diagnose, then off again.
Also set History in days in the execution properties to a sensible value, so the cleanup workflow purges old tasks and logs. Workflow history is otherwise kept, and big histories slow down the console.
Workflow best practices for testing and deploying changes
Adobe is clear that production workflows should not be edited directly, and I would go further: a workflow is code, so treat changes to it the way a developer treats code.
My routine for a change is:
- Make the change on the development instance, in a copy of the workflow.
- Run it against realistic data, with Log SQL queries in the journal and interim populations switched on so I can inspect every step.
- Check the counts on each transition against what the brief expects.
- Switch the debugging options off, then export the workflow in a package with any schemas, options or templates it depends on.
- Import the package on production during a quiet period, and run it once while watching.
For workflows that marketers create again and again, such as a monthly campaign, I build a workflow template under Resources > Templates > Workflow templates instead. The template is tested once, and each new workflow created from it starts with the right activities, names, properties and supervisors. That removes most of the variation that causes problems later.
The other thing I check before deploying is the error management and supervisor settings in the workflow properties. They are the easiest settings to forget, because nothing goes wrong until something does.
Worked example: a nightly chain built to survive a handover
Here is a hypothetical brief. A retailer needs three things every night: load recipient changes from the CRM, as in my recipient import guide, load the day’s transactions, and refresh a “recent buyers” list used by the marketing team in the morning. The current setup is one large workflow with three schedulers that nobody wants to touch.
What I would build instead:
Three workflows, numbered and described. 001 - Import - Recipients from CRM, 002 - Import - Transactions, 020 - Lists - Recent buyers. Each description names its owner and what triggers it.
One schedule. Only 001 has a Scheduler. At its end, a JavaScript activity posts an event to 002’s External signal. 002 does the same for 020. The chain runs as fast as the data allows, and if 001 fails, nothing downstream runs on yesterday’s data.
Run context in one place. 020 starts with a JavaScript activity that sets instance.vars.since to seven days ago, used by its Query.
Errors that reach the right people. All three use Stop the process, with a supervisor group containing the data team. The File collector in 001 uses its no-file transition to alert the CRM team, because a missing file is their problem to solve.
Housekeeping. History set to a few weeks, interim populations off, SQL logging off, End on every branch.
The result does exactly what the old workflow did, but a new team member can read the folder, open any workflow and understand what it does and what it waits for.
Habits that undo workflow best practices
These are the habits I see undo Adobe Campaign workflow best practices most often on inherited instances.
- Editing production workflows directly. Test changes on a development instance and deploy them.
- Leaving old workflows lying around. Delete or archive inactive workflows on production. Unused ones confuse people and sometimes get started by mistake.
- Default activity names.
query11tells nobody anything in an error email. - Supervisors without email addresses. The notification goes nowhere.
Frequently asked questions
Should I use Execute in the engine?
No, not in production. It gives one workflow priority and stops others until it finishes. It exists for debugging.
How do I pass a value from one workflow to another?
Post an event with variables to an External signal activity in the target workflow, using xtk.workflow.PostEvent. The variables arrive in the signal’s outbound transition.
What is the difference between vars and instance.vars?
vars travel with a transition from one activity to the next. instance.vars are shared across the whole workflow run.
How long should I keep workflow history?
Long enough to investigate a problem reported a few days later. A couple of weeks suits most of the instances I have worked on, but match it to how quickly your team reviews issues.
Workflows decide who receives a message. My next post moves to the delivery side and how I set up delivery templates so every send starts from the right defaults.
Last updated on 26 October 2026