The mail-merge functions can be used at three different levels:

    • Using the web interface.
    • By sending DISTRIBUTE jobs to LISTSERV.
    • Using LISTSERV Maestro to construct and send complex mail-merge jobs.

The first two methods will be described in this docurment. For more information about using mail-merge jobs in LISTSERV Maestro, please see the LISTSERV Maestro documentation.

The web interface is ideal when the mail-merge process is supervised by a person, whereas the second method is best suited to automated procedures.

Please read the description of the web interface for background information even if you are only interested in the DISTRIBUTE interface.

1.6.1 Using the Web Interface

The web interface can be used for mail-merge operations where the data source is either a DBMS or a traditional LISTSERV list. That is, if the addresses and names of the subscribers are in, say, a proprietary application that you have purchased or developed, you will need to write a script to extract the information from the application in question and create a DISTRIBUTE job.

To prepare a mail-merge job using a DBMS back-end, go to the following URL:




(depending on your OS of course, the former for Windows, the latter for unix).

You will be prompted to enter an arbitrary SELECT statement to select the recipients who should receive a copy of the message. Every column in the SELECT statement which can be mapped to a character string is then available as a substitution in the message, HTML-style. That is, if you have a column called ACCTNO with the customer’s account number, you can substitute it in the text of your message using &ACCTNO; (a NULL value is mapped to the empty string).

Only the LISTSERV administrator and other users allowed to use DISTRIBUTE (as defined by the DIST_ALLOWED_USERS configuration variable) can use the above URL and send arbitrary DBMS-based mail-merge messages. This is because this method allows you to issue arbitrary SELECT statements, which are not limited to the membership of a particular list. List owners can use another URL to send mail-merge jobs to their respective lists:

http://.../wa.exe?P1&0=M&L=listname (for Windows)

http://.../wa?P1&0=M&L=listname (for unix)

This interface does not prompt you for a SELECT statement – it implicitly selects all the subscribers matching the MAIL/NOMAIL/DIGEST/INDEX subscription criteria. In addition, you can provide a boolean expression to further restrict the recipient list, such as:

(&age < 15) and (&country = Canada)

If the target list is a DBMS list, all the columns in the table which can be mapped to a character string are available as substitutions. If the target list is a traditional LISTSERV list, only &EMAIL and &NAME are available, in addition of course to all the special substitutions, such as &*DATE (see below).

1.6.2 Sending DISTRIBUTE Jobs to LISTSERV

The purpose of this section is to describe the mail-merge enhancements to the DISTRIBUTE function, rather than DISTRIBUTE itself. While it will usually not be necessary to learn all the details of the DISTRIBUTE function to prepare mail-merge jobs, please refer to Section 9 Relayed File Distribution & the DISTRIBUTE Command if you do need further information about DISTRIBUTE.

A traditional (non mail-merge) DISTRIBUTE job has the following format:

//TO DD * Joe Doe Helen Doe
Date: Sat, 4 Jul 2019 22:47:24 +0200
From: XYZnews editor <> Subject: XYZnews issue #215
To: XYZnews recipients

Welcome to issue #215 of XYZnews!

The job must be mailed to LISTSERV, either from the LISTSERV administrator’s address (not recommended) or from an address defined in the DIST_ALLOWED_USERS configuration variable. The personal LISTSERV password corresponding to the sending address must be provided, or the job will be rejected. The “ECHO=NO” option suppresses the message that is normally returned to indicate when the job starts and ends, on the assumption that this information is not wanted (if an error occurs, a message is sent anyway). XYZNEWS-215 is an arbitrary job name for problem tracking purposes.

There are three types of mail-merge DISTRIBUTE jobs:

    • Jobs based on a DBMS back-end with an arbitrary SELECT statement.
    • Jobs where the recipient data is extracted from an existing LISTSERV list (either a DBMS list or a traditional list).
    • Jobs where the recipient data is totally external to LISTSERV and is provided in the job stream, for instance after being extracted from a proprietary customer database with no DBMS functionality.

The only differences are the options provided on the DISTRIBUTE command line, and the format of the //TO section. The format of the message section is the same with all three types of mail-merge jobs.

Note: Some DISTRIBUTE command lines can get quite long and may wrap in your mail client, causing an error when the job is processed by LISTSERV. To avoid this you can use one or more “continuation cards” (see chapter 2.2 of this manual) to split the command over multiple physical lines. See for instance the second example below. DISTRIBUTE Job with DBMS Back-End

These jobs are the simplest and use the following syntax. (This example assumes that your database contains the fields referenced, i.e., EMAILADDR, ACCTNO, NAME, etc.)

//TO DD *
AND ...
Date: &*DATE;
From: XYZnews editor <>
Subject: XYZnews issue #215
To: &*TO;

Welcome to issue #215 of XYZnews!

The DISTRIBUTE command line now reads DISTRIBUTE MAIL-MERGE DBMS=YES, and the recipient section contains one or more SELECT statements. If multiple SELECT statements are included, you must end them with a semicolon, and you may not have more than one statement per line. The &*DATE; and &*TO; substitutions are not required, and simply serve to illustrate the fact that substitutions can now be placed in the message text (including the mail headers).

With the syntax shown above, the first column in the SELECT statement must be the one containing the e-mail address. The other columns are made available as substitutions (&NAME, etc.) This is the recommended syntax when writing a script to prepare the jobs, as only the columns actually used for substitutions are transferred from the DBMS server. Sometimes, however, the script may not know in advance which columns will or will not be used. In this case, a SELECT * may be used, as follows:

//TO DD *
AND ...
//DATA DD * Date: &*DATE;
From: XYZnews editor <> Subject: XYZnews issue #215
To: &*TO;

Welcome to issue #215 of XYZnews!

Note: A “continuation card” was used in the DISTRIBUTE command line above, because the line was so long it wrapped. See Section 2.2 General Syntax Rules for more information on JOB card syntax.)

The EMAIL=EMAILADDR option tells LISTSERV the name of the column containing the e-mail addresses. All the other columns are made available for substitutions, provided of course that they can be mapped to a character string. Note, however, that even large columns (LONG et al.) will be transferred from the DBMS server. As a rule, this syntax should be avoided whenever you would normally avoid doing a SELECT * against the table. DISTRIBUTE Job with Existing LISTSERV List

These jobs use the following syntax:

//TO DD “(&age < 15) and (&country = Canada)”
//DATA DD * Date: &*DATE;
From: XYZnews editor <>
Subject: XYZnews issue #215
To: &*TO;

Welcome to issue #215 of XYZnews!

Please note carefully that the example assumes that the database table containing the list also contains “AGE” and “COUNTRY” fields for the conditional selection.

This job selects all the recipients from the XYZLIST list whose subscription options are either MAIL, DIGEST or INDEX and for which the expression (&age < 15) and (&country = Canada) is true. By default, if you specify only DBMS=LIST(XYZLIST), LISTSERV will only select recipients with the MAIL option. The options you can include in this fashion are MAIL, NOMAIL, DIGEST and INDEX. Note that the FROM=owner-nolist- option has been removed – bounces are automatically integrated with the XYZLIST bounce stream and do not need to be redirected to a change log. If desired, however, you can override this behavior by providing a FROM= keyword.

In addition, you can specify a boolean expression in the //TO section. This expression is in the same format as the conditional expressions used in LISTSERV’s mail template files (described in the list owner’s guide). If you do not want to do any further filtering, just set //TO to the empty string, or to the value “1” (true), for instance:

//TO DD “1”

Please note carefully that for list-based mail-merge it is not sufficient to define “//TO DD *”. This will result in the error “Implicit DD (TO or DATA) not found in job stream.”

For a DBMS list, this syntax is functionally equivalent to a SELECT * job, that is, every column that can be mapped to a character string is available as a substitution. For a traditional LISTSERV list, only &EMAIL and &NAME are available. This can still have its uses, for instance, to send a mail-merge message to AOL recipients only, you could use:

//TO DD “&email =* ‘*@AOL.COM’”

(note the single quotes surrounding the selection criteria--these are required). DISTRIBUTE Job with External Mail-Merge Data

To provide the mail-merge data as part of the DISTRIBUTE job stream, use the following syntax:

//TO DD *
*XDFN NAME=”Joe Doe” AGE=23 country=”Canada” PROBE
*XDFN name=”Helen Doe” Age=47 country=USA
Date: &*DATE;
From: XYZnews editor <>
Subject: XYZnews issue #215
To: &*TO;

Welcome to issue #215 of XYZnews!

With this syntax, all the substitutions are provided in the job, preceding the e-mail address they refer to. There is no DBMS access, no reference to an existing LISTSERV list and no filtering or selection of recipients – you are providing an exact recipient list.

Note that when the DISTRIBUTE command’s FROM= option is set to an owner-xxx address, LISTSERV generates the mail merge message as a passive probe of the recipient. The probe comes at no extra resource cost when sending a mail-merge message.

You can provide multiple *XDFN lines, which can have any number of keyword=value pairs. There must be no spaces either before or after the equal sign. Keywords are not case sensitive, so the case of the keyword name is not relevant. The value must be enclosed in double quotes if it contains spaces, double quotes or backslash characters. To escape a backslash or double quote in such a quoted string, precede it with a backslash. While LISTSERV will support *XDFN lines of up to 64k, bear in mind that you will probably send the job to LISTSERV via e-mail, in which case a lower limit may apply depending on your mail system. It is good practice to keep *XDFN lines to 998 characters or less, as this is the maximum length guaranteed to be successfully transmitted over the SMTP protocol. Automatic Bounce Processing

In most cases, you will want LISTSERV to process all the bounces automatically for you. If you are using the DBMS=LIST syntax, bounces are, by default, integrated with the regular bounce stream for the list in question, and you have access to the full range of LISTSERV bounce processing features (see the description of the “Auto-Delete=” keyword in the list owner’s guide for more information). Otherwise, L-Soft recommends using the “change log” feature in order to keep track of bounces. This is accomplished by including the following keyword in the DISTRIBUTE command line:


where logname is an arbitrary name for the “change log” in which LISTSERV will be recording bounce activity, and hostname is the host name of the machine on which LISTSERV is running. You can use a different log file for every message, one file for each set of related messages, or just one for all the mail-merge messages you send; the decision is left up to you. The file will be located in LISTSERV’s main directory (the one where, among others, permvars.file is located) and will be called nolist- logname.changelog. You do not need to create a list called NOLIST-logname and, in fact, you must not do that. The change log is a standard text file containing entries of the form:

20190704100221 BOUNCE 5.5.0 User Unknown

20190704100223 BOUNCE 552 5.2.2 Mailbox full

LISTSERV will process all bounces silently, and store the bouncing addresses in the change log. Note that there may be other entries in the change log – be sure to ignore any lines which do not contain BOUNCE in the second column (in practice, “nolist” change logs contain only BOUNCE entries, but this could change in a future version). Because mail-merge messages automatically use passive probing, bounce processing is extremely accurate, even if the target mail server normally returns bounces in an unintelligible format.

If you want to process bounces yourself, simply provide a FROM= keyword pointing to the desired target address.

LISTSERV’s BOUNCE records provide the date/time, bouncing email address, and information about why the message bounced, with a syntax of

20190329174013 BOUNCE USER@ZYX.COM x.x.x Bounce Message Here

For example:

20191107112809 BOUNCE BOGUSUSER@RERUN.IN.LSOFT.COM 5.1.1 Mailer [] said: “550 5.7.1 <bogususer@RERUN.IN.LSOFT.COM>... Relaying denied” Using DBMS or List-Based Jobs without Mail-Merge

It is possible to use the DBMS= keyword to extract and select recipients from either a DBMS or a LISTSERV list, without using the mail-merge functions. Simply use the formats shown above, substituting DISTRIBUTE MAIL for DISTRIBUTE MAIL-MERGE. Naturally, you are then unable to put substitutions in the message text, however the message may use significantly less bandwidth and will usually be delivered faster.

1.6.3 Using Substitutions

Mail-merge substitutions work just like HTML substitutions – you embed them in the target message using a sequence such as:


If the substitution is not defined (for instance, using the example &NAME; above), no substitution is performed and the resulting message will contain the literal text ‘&NAME;’. This can happen (still using &NAME; as an example) if there is no column called NAME in the DBMS, and can also happen in individual merge messages if the column called NAME is null for a given user. (Remember that a null value is not the same as a blank value in most DBMS products.)

In addition to the substitutions provided through the DBMS, list or DISTRIBUTE job stream, a number of special substitutions are always available:

&*DATE; is an RFC822 date field (without the “Date:” keyword) suitable for insertion in mail headers. This makes it easier to develop scripts that prepare DISTRIBUTE jobs, and there is no performance penalty as this is a “false substitution” – one that has the same value for all recipients and is pre-processed by LISTSERV. By the time the outbound mailer gets the message, it no longer contains a substitution.

&*WA_URL; is the URL of the LISTSERV web interface script, up to and including the script name. You would typically add a question mark and parameter list afterwards. As it is a false substitution, there is no performance penalty for using it. If the web interface has not been installed, it translates to the empty string.

&*TO; is the e-mail address of the current recipient, suitable for insertion in a “To:” field.

&*INDEX; is a unique, random number ranging from 1 to the total number of recipients. It can be used in conditional blocks (see below) to send a particular message to a random sample of recipients – an invitation to preview a new web site or an ad banner, for instance.

&*URLENCODE(); is a function which can be used to assist you in passing URLs that contain spaces or special characters. The function encodes the passed value so that it is valid in a URL (replacing special characters with “%” followed by two hexadecimal digits forming the hexadecimal value of the character). For instance, this works for substitutions such as &*URLENCODE(&ID;); where &ID; represents a field extracted from a database. A specific example would be to encode a URL pointing to a file with spaces in its name, for instance, a file called Year 2000 figures.html. This file name could be converted for you and placed into a URL in your mail-merge message like this:*URLENCODE(Year 2000 figures.html);

resulting in the URL being correctly displayed as in your message.

&*TICKET(listname); and &*TICKET_URL(listname,command); are special substitutions used to issue command tickets, which are described below. Using Command Tickets

Documented Restriction: While it is possible to tell LISTSERV to issue a command ticket to a list owner address, for security reasons list owners cannot use command tickets for authentication of commands sent for the lists they own. If a list owner attempts to use a ticketed command on a list he owns, LISTSERV will respond

For security reasons, list owners may not issue ticketed commands. The ticket was otherwise valid - this is not an error on your part.

When testing ticketed commands it is thus necessary that the list owner do the testing from an account that is not listed in Owner= for the list in question.

Command tickets allow you to provide safe, authenticated, single-click subscribe, unsubscribe or SET commands in a mail-merge message. They are implemented through a two-step mechanism. In the first step, LISTSERV generates a cryptographically protected ticket through a mail-merge substitution. This ticket allows anyone to execute a limited set of commands on behalf of the user for whom it was issued, and only for the list for which the ticket was issued. Typically, you would use the substitutions to construct a URL, or perhaps a HTML form with a number of buttons triggering various changes in subscription options or status.

In the second step, the user activates the URL or form, which presents the ticket to LISTSERV for verification. If the ticket is valid and has not expired yet, the command is executed. There is no need to use passwords or OK confirmations. Tickets are safe because they are, by design, only ever mailed to the person for whom they are issued. They are also limited to a particular list, and may only be issued at the request of the owner of that list (who does not need to use tickets to affect the user’s subscription to the list in question). Finally, tickets expire after a month, to limit the impact of incidents where printouts of e-mail messages were misplaced, etc.

A ticket allows the use of SUBSCRIBE, SIGNOFF and SET commands, with arbitrary parameters. However, the name of the list is extracted from the ticket, and may not be changed. In fact, you do not specify the name of the list in the command. The simplest way to format a ticket URL is as follows:

<a href=”&*TICKET_URL(XYZ-L,SET NOMAIL);”>Turn off mail receipt</a>

This substitution expands into a URL which will execute a SET XYZ-L NOMAIL command when activated. To use a ticket in a form with several buttons, you could do as follows:

<form action=”&*WA_URL;”>
<input type=hidden name=TICKET value=”&*TICKET(XYZ-L);”>
<input type=hidden name=L value=”XYZ-L”>
<input type=submit name=c value=”Unsubscribe”>
<input type=submit name=c value=”SET NOMAIL”>
<input type=submit name=c value=”SET MAIL”>

You could also use an image map pointing to a script of your own making, a Java applet, etc. To execute the command, simply redirect the browser to:


The list name is optional: it is not required to use the ticket, but can be provided to direct the LISTSERV web interface to use the HTML templates for the list in question. You can customize the response on a per-list basis using the standard web template Customization features, which are described in the list owner’s guide. Performance Considerations with Command Tickets

Unlike OK cookies, command tickets do not use up any disk space or require any kind of house-keeping. You can use them as often as you wish without ending up with gigabytes of pending cookie data on your LISTSERV server, as would be the case with OK cookies and a large daily newsletter offering multiple one-click commands. However, being cryptographically protected, they do require some amount of CPU time for generation, without which they would not provide much protection at all. So, do not worry if LISTSERV does not acknowledge execution of your 500,000 recipient job after 30 seconds as usual.

LISTSERV will generate the tickets only once, even if you refer to the same ticket multiple times in the message. Thus, you do not need to worry about using tickets in a single big form rather than multiple smaller forms, or once at the top and once at the bottom of the message. While it does cost a small amount of resources for the ticket to be merged with the message text in multiple locations, the CPU-intensive ticket generation only occurs once.

If you have a very large list which must be delivered very quickly at a specific time, as can often be the case in the news industry, command tickets may present a challenge. In this case, the best solution is to make use of the SMTP worker pool feature, rather than the “PRIME=” job option, to schedule the delivery of the message. Whereas “PRIME=” will hold the entire job and require you to estimate execution time in order to open the execution window long enough in advance, the SMTP worker pool feature can be used to create a worker pool which does not begin delivery until a specific time. You can then execute the job long in advance, but suspend delivery until the desired time. Advanced Substitutions

In addition to the above, two further built-in mail-merge substitutions and a related (optional) DISTRIBUTE keyword are available. They were added to solve concerns about “(no name available)” appearing in the To: field, and at the same time the flaw in using:

To: “&NAME;” <&*TO;>

which is not correct for all possible values of &NAME. This feature adds one optional DISTRIBUTE keyword:


For instance,


This indicates the name of the XDFN/DBMS field containing the name of the recipient. If absent, the name is unknown (see below).

In the case of DBMS=LIST, the default value of NAMEFIELD=xxx is set automatically from the “DBMS=” keyword and/or the system defaults found in SITE.CFG. Note that the correct syntax is NAMEFIELD=NAME, not NAMEFIELD=&NAME; or similar.

NAMEFIELD=xxx is not ignored for a list distribution. Any available column name can be specified for NAMEFIELD, at the risk of making a mistake. The design assumption was that in some cases there might be several name columns in the table, for instance with different character sets or one with and one without accents. It was thought best to allow this to override the internal default, even if the default is correct. However, normally one should omit NAMEFIELD=xxx for a list distribution and LISTSERV will provide the correct value.

Two additional substitution variables are available:

    • &*NAME; is replaced with the variable specified in the (new) DISTRIBUTE option NAMEFIELD=xxx. If unknown, the empty string is substituted as a constant. This is just a convenient way to refer to the name field in examples or generic jobs, regardless of what it is really called.
    • &*TOFIELD; is a correct RFC822 to field (without the “To:”) for the supplied name and e-mail address. If the name is unknown or missing, the result is the same as &*TO. A missing name is NULL, the empty string or ‘(no name available)’. To clarify, the correct use is:


Note: There is a performance cost for this option. The RFC822 rules are somewhat time-consuming; additionally, this also requires parsing XDFN lines to extract the name field (when not needed, LISTSERV adds its own XDFN). Finally, the NAME field is passed even if it is only used for &*TOFIELD.

1.6.4 Using Conditional Blocks

In addition to simple substitutions, you can include “conditional blocks” in your mail- merge messages. A conditional block is a group of lines which is only included if certain conditions are met. Here is an example of a mail-merge message with conditional blocks:

Date: &*DATE;
Subject: Happy birthday, &FNAME;!
To: &*TO;

Happy birthday!
To celebrate the occasion, XYZweb is pleased to credit your account with $10.00 in birthday credits. You can spend these credits anywhere in our online store, but there’s a catch! They will expire in a week if you do not use them! So wait no further and go to your personalized web page at:


.* Special offer for people who turn 18
.bb &age = 18
Now that you are 18, you can finally do what you have been waiting for all your life - sign up for your very own XYZweb online cash management account! We are waiving the first year’s fee if you apply within the next 2 weeks! Apply now at:


.* Special offer for younger children, but not around Christmas though
.bb (&age <= 10) and (DEC not in &*DATE)
Congratulations! You have won an XYZweb teddy bear! Order it now by going to:


.* Two randomly selected people every day get a free T-shirt
.* Note: &*index is randomized with every run. If we ran the job
.* twice, the prize would go to different people
.bb &*index <= 2
Congratulations! You have won a free XYZweb T-shirt!...
.* Another 10 randomly selected people get a free baseball cap
.bb (&*index > 2) and (&*index <= 12)
.* Make that a free pair of sunglasses in Texas!
.bb (&country = USA) and (&state = TX)
Congratulations! You have won a free pair of XYZweb sunglasses!...
Congratulations! You have won a free XYZweb baseball cap!...

.* Plug our travel partner if user checked TRAVEL category on web
.* signup form
Are you by any chance making travel plans? If so, our partner, ZYX Travels, have a special offer for you! Simply follow this URL for more information:


.* Special for AOL users
.bb &*to =* “*”
Did you know that you can access XYZweb’s store directly from AOL? Simply do...
.* That’s it!

A conditional block starts with a .BB (begin block) statement, may include a .ELSE statement, and ends with .EB (end block). The .BB statement contains a conditional expression in the format used in LISTSERV mail templates (described in the list owner’s guide). If it evaluates to TRUE, the text is included for the recipient in question, otherwise it is skipped. Using &*INDEX to select random samples

Every recipient is assigned a unique, random number ranging from 1 to the total number of recipients. Or, in other words, every number from 1 to the total number of recipients is assigned to a particular, randomly selected recipient. This number is called &*INDEX, and can be used in conditional blocks to select random samples of recipients. Typically, you will use a sequence such as the following:

.BB (&*index >= min) and (&*index < max)

The conditional block will be sent to all the recipients whose index ranges from min (inclusive) to max (exclusive). Thus, it will be sent to max-min recipients. Using the PARTS option

While traditional LISTSERV lists provide support for up to 23 topics, there are cases where LISTSERV’s topic functions are difficult to use. For instance, imagine a web site where people can subscribe to news about movie stars. Presumably, while there would be hundreds of actors to choose from, most people would only be interested in a handful of them. However, it is likely that a small number of people will be interested in dozens of actors

LISTSERV’s built-in topic support cannot be used, because there will be a lot more than 23 topics. The straightforward approach is to create one mailing list for each actor, and this will work well for people who are only interested in a few actors. However, the avid fans who subscribed to dozens of actors may resent the number of individual messages they receive from the service, and sign off. This would be unfortunate, as they probably represent a non-negligible fraction of the purchases of fan-related material.

The PARTS feature allows you to define any number of topics in the mail-merge message, using conditional blocks of the form:

.BB Maxine_Bahns

To determine whether a particular recipient is subscribed to news about this actress, LISTSERV checks whether the comma-separated PARTS field contains the topic Maxine_Bahns. For a mail-merge job using a DBMS back-end, the PARTS field is a DBMS column, whose name is specified either in the web interface form or in the “DBMS=” keyword on the DISTRIBUTE command, as follows:


In practice, you would probably maintain the mapping between subscriber and actors using a separate table and foreign keys, but LISTSERV cannot make use of this kind of data layout efficiently, as it would require issuing one SELECT statement per recipient. However, you could use a trigger procedure to update the comma-separated ACTORS column whenever a change is made to the mapping table.

In a mail-merge job with external data source (that is, a job which includes *XDFN declarations), you include the PARTS field in the job stream, with the following syntax:

*PARTS part1,part2,...

This declaration can come either before or after the *XDFN lines, however it can occur only once.

In some cases, you may want to send some information to people who subscribe to any of a variety of topics. You can do so using the &*PARTS substitution and the IN operator of the LISTSERV expression evaluator. LISTSERV sets &*PARTS from the PARTS field, but replaces all commas with spaces in order to allow the use of IN/NOT IN. For instance, you could have the following:

.BB (Maxine_Bahns in &*PARTS) or (Edward_Burns in &*PARTS)
Maxine Bahns and Edward Burns have broken up! Millions of fans were in turmoil as the news was announced today at...

You should use the simpler syntax whenever possible, because it allows LISTSERV to bypass the expression evaluator. While very fast, the expression evaluator does add some overhead considering that typical PARTS jobs will have scores of different topics, which must be evaluated for each of the hundreds of thousands of recipients. Thus, the likely order of magnitude of the number of evaluations is in the 1-10 million range.