I am getting a list of expired users from third party so i have list of Email Id's for which i need to delete the contacts from Tridion Database tables (Sub management, CM_Email , Contacts, extended_details etc )

I will schedule this application so that all the InActive Users (Which I am getting from third party) should be deleted on daily basis from Tridion. I can see Contact.GetContact method in Tridion.Audiencemanger.Api dll but it needs a UserContext or a filter , I didn't find any method to delete by email.

  • Could you describe the use case? I'm curious at how the 3rd party manages the deletion status for Contacts and if existing or custom Audience Manager data could have worked. Dec 5, 2018 at 21:58
  • When you say "Email Id's" do you mean their e-mail addresses? Dec 6, 2018 at 9:33
  • @PeterKjaer Yes, thats correct Dec 6, 2018 at 13:46
  • @AlvinReyes We have some forms, when an end user submit a form , data ends up in Custom Db as well as Audience Manager. Custom Db managed by some other Team , and they want to delete some Records from custom Db and Audience Manager but by a schedule service. This schedule service will pick the Email id's from third party or custom Db and will delete the records from everywhere. Dec 6, 2018 at 13:49
  • Okay, makes sense and thanks for the description. It sounds like an interesting self-service setup. Dec 6, 2018 at 21:43

2 Answers 2


Keep in mind that if you delete a Contact, that's it. There is no audit trail and the data is lost forever. Doing that automatically based on input from an external source could lead to trouble (i.e. data being lost because of incorrect or unexpected input).

So another option that I would like to propose would be to create a "To be deleted" Address Book (you can name it whatever you like) and to not synchronize it anywhere and not add it to any Publications. Just leave it in the System Administration section.

Then write a small tool that uses the Import functionality of the API to move those Contacts to the new Address Book. Since they will be removed from their original Address Book into one that isn't synchronized anywhere, they will be removed from the website(s) too. And since you won't add it to any Publications, they won't be included in any Mailings (if you use Outbound E-mail).

Once you are confident that those Contacts can indeed be permanently deleted, you can then do so easily as they are all in the "To be deleted" Address Book. You can even do that automatically (see Saunders' answer) -- though again I would try to at least add some audit trail or extensive logging in case you need to recreate the Contact.

The import functionality works with CSV files and is pretty easy to use. I spent a few minutes creating this tool that does exactly what you are asking for, when given a file with the e-mail addresses of the Contacts to move:

using System;
using System.Globalization;
using Tridion.AudienceManagement.API;
using Tridion.AudienceManagement.API.Import;

namespace PseudoDelete
    class Program
        // Set this to the Key field of your "To be deleted" Address Book
        private const string AddressBookKey = "TBR";

        private static void Main()
            Console.WriteLine($"Import started by {UserContext.Current.UserName}...");


            if (System.Diagnostics.Debugger.IsAttached)
                Console.WriteLine("Press ENTER to exit");

        private static void UpdateContactsToRemove()
            ImportManager manager = new ImportManager(UserContext.Current);

            // This file must be present in the ImportFolder configured in OutboundEmail.xml
            ImportDefinition definition = manager.GetImportDefinition("ContactsExport.csv", ImportType.ImportContacts);

            // Auto match the import file (Matches fields from the import file with equally named database fields).

            // You have to set this to the 'All Contacts' Address Book in order to move Contacts across Address Books
            definition.AddressBookUri = TcmUri.NullUri;

            // Change the Address Book of the Contacts (i.e. move them)
            definition.Mappings["group_keyvalue"].SourceField = null;
            definition.Mappings["group_keyvalue"].DestinationField.DefaultValue = AddressBookKey;

            // Hook into the feedback event to show some feedback
            manager.Progress += ImportProgress;

            // Import and report the results
            ImportResult results = manager.ImportContacts(definition);
            Console.WriteLine(results.AllDataImported ? "All Contacts were successfully imported" : string.Format(CultureInfo.InvariantCulture, "Contacts were imported, but some errors occurred, please check the {0} and {1} log files", definition.ErrorFile, definition.LogFile));
            Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Imported {0} new contacts.", results.ContactsCreated));
            Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Updated {0} existing contacts.", results.ContactsUpdated));


        private static void ImportProgress(object sender, ImportEventArgs e)
            if (e.Progress > 0)
                Console.WriteLine(string.Format("[{0}] > {1} rows copied)", e.Description, e.Progress));
                Console.WriteLine(string.Format("[{0}] {1}: {2} {3}", DateTime.Now.ToString("s"), e.ImportStep.ToString().PadRight(22, ' '),
                    e.Description, e.Starting ? "(starting)" : "(complete)"));

If you have no control over the file and the columns aren't named the same as your fields, you'll need to map the column in the file to the field in the database. You do that similar to how we set the "group_keyvalue" field -- you just need to set the SourceField like this:

definition.Mappings["name-of-field-in-AM"].SourceField = definition.SourceFields["name-of-column-in-the-file"];

This approach will be very fast (I just moved 10.000 Contacts in about a second), whereas looping over them one by one will not be.

Good luck! :)


Have you tried this? REMEMBER TO ADD A CONDITION - and that you need to run for each address book)...

ContactFilter filter = new ContactFilter(UserContext.Current);

// Create a copy of the streamed list
List<Contact> contactsToDelete = Contact.GetContacts(UserContext.Current, filter, addressBook).ToList();

// Loop over the new list and delete each Contact
foreach (Contact contact in contactsToDelete)

Compliments of Peter Kjaer from a quick google - it's an old one but... maybe... .. https://pkjaer.wordpress.com/2011/12/01/looping-through-contacts/

  • 1
    This is indeed a valid approach. You'll have to evaluate if it will be faster to loop through all Contacts in an Address Book and compare it with the list, or loop through the list and look up each Contact by its e-mail address (using the filter). It depends on which list is likely to be bigger -- I imagine the second approach would be faster overall. Dec 7, 2018 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.