SmtpAddJob Method (String, String, EmailAddressCollection, DataTable, Object, Boolean, Boolean)
Puts a "mail merge over database" job onto waiting list for subsequent processing in bulk mode.

Namespace: MailBee.SmtpMail
Assembly: MailBee.NET (in MailBee.NET.dll) Version: 11.2.0 build 590 for .NET 4.5
public void AddJob(
	string tag,
	string senderEmailPattern,
	EmailAddressCollection recipientsPattern,
	DataTable mergeTable,
	Object mergeRowIndices,
	bool keepProducedJobs,
	bool keepMergedData


Type: SystemString
Any string the developer wants to assign to Tag property of SendMailJob object created by this method. The developer can leave it a null reference (Nothing in Visual Basic).
Type: SystemString
The e-mail address template of the sender. If it's a null reference (Nothing in Visual Basic), the e-mail address template will be taken from From property.
Type: MailBee.MimeEmailAddressCollection
The e-mail address template of the recipients list. If it's a null reference (Nothing in Visual Basic), the recipients list will be constructed via merge of To, Cc, and Bcc patterns with actual values from the data source.
Type: System.DataDataTable
The data source for mail merge.
Type: SystemObject
The comma-separated list or integer array of indices of rows of mergeTable to be processed, or a null reference (Nothing in Visual Basic) if the entire mergeTable should be processed.
Type: SystemBoolean
Indicates whether MailBee should create separate SendMailJob object for every e-mail message produced during mail merge. If true, JobsSuccessful and JobsFailed collections will end up with the same number of jobs as the number of e-mail messages generated; otherwise, only SendMailJob object originally added by this method will end up in JobsSuccessful or JobsFailed collections.
Type: SystemBoolean
Indicates whether MailBee should retain merged messages after they have been processed. If true, MailBee will not clear MergedXXX properties of SendMailJob object after the e-mail message associated with this object has been processed; otherwise, MailBee will clear memory occupied by the merged message so that MergedXXX properties start returning null once the message has been processed.
MailBeeInvalidArgumentExceptionmergeTable is a null reference (Nothing in Visual Basic) or mergeRowIndices is invalid value (for instance, neither string nor array of integers).

This method allows the application to schedule mail merge over database for subsequent processing with SendJobs, BeginSendJobs(AsyncCallback, Object), or SubmitJobsToPickupFolder(String, Boolean) method.

Note Note
To perform mail merge immediately rather than schedule it, use SendMailMerge(String, EmailAddressCollection, DataTable) method.
Note Note
This method is not available in .NET Core and UWP editions.

The mail merge requirements:

  • The template of e-mail to be merged with mergeTable data must be specified in Message property. Normally, Message property specifies the e-mail message to be sent out by Smtp object. In the case of mail merge, this e-mail message is considered as a template where ##Column_Name## patterns will be replaced with the actual data from database table rows during the mail merge process.
  • Smtp.DeliveryNotification.TrackingID may also contain a pattern if desired. Thus, it's possible to automatically assign unique tracking ID to the each message sent during mail merge and then receive this ID with bounced messages.
  • mergeTable must be the data source containing all the data rows for which e-mails should be generated and sent; typically, this data source is a result of SQL query.
  • Names of columns of mergeTable must match pattern names in the e-mail template. For instance, if there is Email column in the data table and the developer wishes to make values from this column appear in resulting e-mails, then the e-mail template must contain ##Email## string in the places where the e-mail address needs to be placed. Patterns are case-sensitive! For instance, if data table column name is Email, only ##Email## pattern will be replaced with the column value. ##email## and ##EMAIL## patterns will be left intact.
  • senderEmailPattern can be left null. However, if resulting e-mails should actually be sent from another e-mail address (for instance, resulting e-mails have in "From:" field while you wish to send them from ""), then it's required to specify senderEmailPattern. It can contain a pattern (e.g. ##Something##) or refer to the actual value (e.g.
  • recipientsPattern have the same meaning for specifying the actual recipients as senderEmailPattern - for the actual sender. For instance, to set the pattern which will take the 1st recipient's e-mail address from Email field of the data table while the 2nd recipient's email address is hard-coded, pass the following as recipientsPattern value: new EmailAddressCollection("##Email##,").
  • Setting tag to non-empty value helps to distinguish the current mail merge from other jobs if there are any. This is useful if the application submits and processes multiple jobs and the developer wants to track processing of each job separately. Jobs tags also appear in the log if logging is enabled.

mergeRowIndices parameter can be used to specify which rows of mergeTable should be processed. For instance, consider the following scenario: the application initially performed the mail merge with not 100% positive results (some e-mails failed to be sent), and it's desired to run it again to try to re-send initially failed e-mails. This can be done with RetryFailedJobs method but this requires calling RetryFailedJobs in the same application instance that was used to run the initial mail merge. If the initial instance of the application shuts down after performing mail merge, the application should save failed data row indices in a persistent storage (database, file, etc) on exit, and then load these data row indices back when another instance of the application starts. See the example below for details.

keepProducedJobs parameter controls what to do with SendMailJob objects created during mail merge process. When a mail merge job is submitted to JobsPending collection, this job represents the entire mail merge task. Once processing of this job starts, MailBee "bites" a single data row index from this job and creates new SendMailJob object for the e-mail template and this data row. The original mail merge job is moved into JobsRunning (and then into JobsSuccessful or JobsFailed) only when a single data row remains in it. Thus, MailBee splits large mail merge job into many smaller jobs (each processes a single data row) during processing. This allows the developer to easily monitor the status of each e-mail produced by mail merge process and otherwise manage mail merge processing on per e-mail level. However, if the application does not need this, it may set keepProducedJobs to false to automatically remove SendMailJob objects created during processing mail merge when they are no longer needed. This way, only the original SendMailMerge(String, EmailAddressCollection, DataTable) object will be put in JobsSuccessful or JobsFailed collections once the mail merge has been finished. This object will refer to the last data row and the last e-mail message generated during mail merge process.

As you can see, when keepProducedJobs is false, the last sub-job matches so that overall number of items in all collections like JobsPending, JobsFailed, and JobsFailed won't change, the items will just be moved among these collections.

You can also manually control whether to keep jobs after processing with FinishingJob event. For instance, you can make MailBee remove even those jobs which haven't been created by mail merge process (so that total number of all items in all jobs collections upon completion will be zero). This can be useful for the optimization of memory use.

keepMergedData parameter can be set to true to tell MailBee not to clear memory occupied by e-mails generated during mail merge. However, this may cause out-of-memory issues if the size of each generated e-mail message or their number was large.

Still, if's safe to set keepMergedData to true if keepProducedJobs is false. In this case, only a single SendMailJob object will be added to JobsSuccessful or JobsFailed collections so that only a single merged e-mail message will reside in memory.

If, for some reason, you need to retain all the e-mails sent (or not sent) during mail merge, the best approach is to subscribe to MessageSent and MessageNotSent events and perform what you need in the event handlers. Even if keepMergedData is false, MailBee won't clear memory occupied by generated e-mails until MessageSent and MessageNotSent have been raised.

The above is true for keepProducedJobs parameter as well. E-mails generated during mail merge won't be discarded until MailBee has raised MessageSent and MessageNotSent (or any other events which occur before MessageSent or MessageNotSent).

If the application hasn't subscribed to any of these events (and keepProducedJobs or keepMergedData is false), MailBee will dispose objects which are no longer necessary when appropriate.


This console sample performs mail merge in two runs. On the second run, all the data rows which failed to be sent out as e-mail messages on the first run are tried again. The application may complete in a single run if all the data rows have been successfully sent as e-mails within the first run.

This sample does NOT use RetryFailedJobs method to put failed jobs back into the pending queue. Instead, it demonstrates the approach which would allow the application to terminate after the first run and then retry mail merge of failed data rows later in a separate run of the application. In this sample both runs actually occur within a single application instance but the application structure is ready for making every run occur within separate instances. The sample shows how to get the list of failed data rows into a string and then load it from a string. In a real application, this string will be saved in a file or database before the application terminates first time, and then loaded back from this file or database when the application starts again next time.

using System;
using System.Data;
using System.Data.OleDb;
using MailBee;
using MailBee.Mime;
using MailBee.SmtpMail;

class Sample
    // Reports successful attempt of sending e-mail.
    static void mailer_MessageSent(object sender, SmtpMessageSentEventArgs e)
        // Display e-mail address of the successful e-mail.
        Console.WriteLine(e.MergeTable.Rows[e.MergeRowIndex]["Email"] + " of '" + e.Tag + "' job SUCCEEDED");

    // Reports failed attempt of sending e-mail.
    static void mailer_MessageNotSent(object sender, SmtpMessageNotSentEventArgs e)
        // Display e-mail address of the failed e-mail.
        Console.WriteLine(e.MergeTable.Rows[e.MergeRowIndex]["Email"] + " of '" + e.Tag + "' job FAILED");

    static void Main(string[] args)
        Smtp mailer = new Smtp();

        // Logging into a file is useful for troubleshooting.
        mailer.Log.Filename = @"C:\Temp\log.txt";
        mailer.Log.Enabled = true;
        mailer.Log.Format = LogFormatOptions.AddContextInfo;

        // Uncomment the line below to use unlimited number of worker threads (up to 60)
        // and increase performance. Note that not all SMTP servers support this.

        // mailer.MaxThreadCount = -1;

        // Subscribe to events to track send bulk mail progress.
        mailer.MessageSent += new SmtpMessageSentEventHandler(mailer_MessageSent);
        mailer.MessageNotSent += new SmtpMessageNotSentEventHandler(mailer_MessageNotSent);

        // Setup SMTP server parameters.
        mailer.SmtpServers.Add("", "jdoe", "secret");

        // Setup e-mail message header template for mail merge.
        mailer.Message.From.AsString = "John Doe <>";
        mailer.Message.To.AsString = "##Name## <##Email##>";
        mailer.Message.Subject = "Our Jan/2007 newsletter";

        // Setup DSN template for mail merge. In particular, this can be useful
        // to track bounced messages which may come back from some addresses after
        // sending bulk mail out. If the SMTP server does not support DSN, this
        // setting will be ignored.
        mailer.DeliveryNotification.TrackingID = "Jan2007_##ID##";

        // Setup HTML body template.
        mailer.Message.BodyHtmlText = "<html>##Body##</html>";

        // Setup template for adding file attachments upon the specified path.
        // In this sample, the path to attachment files will be constructed as
        // "C:\" + DatabaseRecordField("Doc_path").

        // Make outgoing e-mails UTF-8 to allow content in any language.
        mailer.Message.Charset = "UTF-8";

        // Tell MailBee to generate alternative plain-text version
        // of each e-mail automatically.
        mailer.Message.Builder.HtmlToPlainMode = HtmlToPlainAutoConvert.IfHtml;

        // Specify database connection string (it may be different in your case).
        string connParams = @"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\Temp\newsletter.mdb;";

        // Indices of the data rows which failed during the first run.
        // If null, all indices will be processed.
        string failedIndices = null;

        DataTable table = null;

        // Make two runs of mail merge. If e-mails created from some data rows fail,
        // we'll attempt to resend them on the second run.
        for (int i = 0; i < 2; i++)
            // Connect to the database and populate mail merge job to-do list with
            // the data from "mailing_list" table.
            using (OleDbConnection conn = new OleDbConnection(connParams))
                // Open the connection and get the data.
                OleDbCommand command = new OleDbCommand("SELECT * FROM mailing_list", conn);
                OleDbDataAdapter adapter = new OleDbDataAdapter();
                table = new DataTable();
                adapter.SelectCommand = command;

                // Create a job which is the following task for MailBee: perform mail merge over
                // the specified data table and send out each resulting e-mail to
                // the recipients which appear in the resulting messages. ""
                // address will be used as Return-Path (i.e. sender e-mail address).
                mailer.AddJob("My", "", null, table, failedIndices, true, false);

            // Run the job. The actual mail merge takes place here.

            // Report results (row indices in the data table) to the console.
            if (mailer.JobsFailed.Count == 0)
                Console.WriteLine("All of the rows of the table have been processed and sent as e-mails.");
                if (mailer.JobsSuccessful.Count == 0)
                    Console.WriteLine("None of the rows of the table has been processed and sent as e-mail.");
                    Console.WriteLine("Not all rows of the table have been processed and sent as e-mails.");

                    Console.WriteLine("Successful rows: ");
                    Console.WriteLine(mailer.JobsSuccessful.GetIndicesAsString(table, "My"));

                    Console.WriteLine("Failed rows: ");
                    Console.WriteLine(mailer.JobsFailed.GetIndicesAsString(table, "My"));

                // Remember failed data rows and clean-up the collections of the results.
                // Actually, we could simply call mailer.RetryFailedJobs instead. We're using
                // 'failedIndices = mailer.JobsFailed.GetIndicesAsString' to demonstrate
                // how this could be done if the application terminates after the first run and
                // then starts again for the second run (which retries processing the failed
                // data rows). In this case, we need to somehow pass the list of failed data rows
                // between two instances of the application. To accomplish this, failedIndices can
                // be written into a file in the end of the first run of the app and then read from
                // the file in the beginning of the second run.
                failedIndices = mailer.JobsFailed.GetIndicesAsString(table, "My");
See Also