How to Build a Custom Coupon Module in Adobe Campaign Without the Standard Package
While Adobe Campaign Classic’s Coupon Management Package offers structured functionality for handling vouchers, it often comes at a price. Enabling this module usually requires additional licensing or professional service consultations, which can delay delivery and inflate project costs. When you’re under time pressure or need a fast, flexible solution, reaching for the standard package might not be your best move.
In the corporate world, such additions often need to go through multiple layers of approval, budgeting, and procurement—sometimes taking weeks or even months. But when a campaign manager comes in on Monday asking for vouchers to go live next week, there’s simply no time to wait. In these situations, you need a fast, flexible alternative that you can fully control and deploy without external dependencies.
In this article, I’ll walk you through how to build a custom coupon module in Adobe Campaign Classic—using native features only. No add-ons. No license barriers. Just a practical solution that works under pressure.

Create List to hold vouchers
First, we need to create a dedicated list (or custom table) to store the vouchers. The structure should include at least the following columns:
- Voucher Code
- Date Assigned
- Recipient Identifier (this can be the primary key from your recipient table, e.g.
@id)
You can also add additional columns based on your business needs, such as:
- Order Number (if the voucher is linked to a transaction)
- Secondary Recipient Identifier (e.g., email or CRM ID)
- Status (e.g., Available, Assigned, Redeemed, Expired)
This setup allows you to track the lifecycle of each voucher and maintain a clean assignment history.
When structure is decided, the last step is to load the new vouchers using a simple file load activity.

Load vouchers– csv file with only one column called voucherEnrichment: add voucher fields to the structure– Add any additional voucher fields to the structure by simply including empty columns you’ll need later. For text fields, use''(empty string), and for numbers, use0. This ensures the list automatically sets the correct data type for each column
Assign vouchers in workflow
Now that we have our vouchers loaded into the list, we’ll use them in our campaign workflow in the next section. First, we add an empty enrichment activity to generate a new temporary table, and we name it enrichTargetData.
TIP: The Name refers to the internal name of the activity. You need to open the activity and define it in the Advanced tab.
in our case we called it First, we assign the vouchers, then fork the population. The top branch is used to create a new transition, where we replace its population with today’s assigned vouchers. This helps with quicker assignment or in cases where a recipient already received a voucher in a previous send—we simply give them another one.
The bottom branch contains our original targeting population. In the enrichment activity, we perform an inner join between the vouchers and the target population to create a new column called voucher. Once this is done, we can use the voucher directly in the email template where needed.

JS – assign vouchers
This script automates the assignment of voucher codes from a list (grp1000) to recipients fetched from temporary workflow data (enrichTargetData). It matches vouchers one-to-one and records the assignment for future reference or exclusion.
var results = sqlSelect("resultQuery,@voucher:string",
"select svoucher from grp1000 where iRecipientId = 0 LIMIT 2000"),
vouchers = [],i, sql;
for each(var result in results.resultQuery)
vouchers.push(result.@voucher)
var selectExpr = [
{ expr: '@id'},
{ expr: '@orderNumber'}
],
nlQueryDef = new NL.QueryDef("temp:enrichTargetData", selectExpr, []),
target = nlQueryDef.create().execute().data,
d = new Date();
if (vouchers.length < target.length) logError("Not enough vouchers.");
for(i=0;i<target.length;i++){
sql = "UPDATE grp1000 SET sordernumber='"+ target[i].orderNumber +"' + ", irecipientid=" + target[i].id + ", tsdate=CURRENT_TIMESTAMP WHERE svoucher='"+vouchers[i]+"'";
sqlExec(sql)
}
sqlSelect()pulls up to 2000 unassigned vouchers (iRecipientId = 0) from thegrp1000table.- Each voucher is expected to be stored in the
svouchercolumn. - Results are stored in
results.resultQuery, and individual voucher values are pushed into thevouchersarray. - A QueryDef is created to fetch data from a temporary table called
enrichTargetData(populated earlier in the workflow). - It selects
@idand@orderNumberfor each recipient - This data becomes the list of recipients who need a voucher.
- Ensures that there are enough vouchers for the recipients.
- Logs an error if the voucher list is too short.
- Loops over each recipient in the target list.
- Builds an
UPDATEquery to: - Assign the voucher (
svoucher = vouchers[i]) to the recipient - Set:
sordernumber(order number of recipient)irecipientid(recipient ID)tsdateto current timestamp
- Runs the
sqlExec()to update the database.
Further Improvements to the Custom Voucher Module
To make your custom voucher module more robust, you can
đź”’ This content is for Premium Subsribers only.
Please log in to preview content. Log in or Register
You must log in and have a Premium Subscriber account to preview the content.
When upgrading, please use the same email address as your WordPress account so we can correctly link your Premium membership.
Please allow us a little time to process and upgrade your account after the purchase. If you need faster access or encounter any issues, feel free to contact us at info@martechnotes.com or through any other available channel.
To join the Discord community, please also provide your Discord username after subscribing, or reach out to us directly for access.
You can subscribe even before creating a WordPress account — your subscription will be linked to the email address used during checkout.
Premium Subscriber
19,99 € / Year
- Free e-book with all revisions - 101 Adobe Campaign Classic (SFMC 101 in progress)
- All Premium Subscriber Benefits - Exclusive blog content, weekly insights, and Discord community access
- Lock in Your Price for a Full Year - Avoid future price increases
- Limited Seats at This Price - Lock in early before it goes up









