diff --git a/client/src/campaigns/CUD.js b/client/src/campaigns/CUD.js
index ad0f81b4..2782ac3e 100644
--- a/client/src/campaigns/CUD.js
+++ b/client/src/campaigns/CUD.js
@@ -15,6 +15,7 @@ import {
     ButtonRow,
     CheckBox,
     Dropdown,
+    Fieldset,
     Form,
     FormSendMethod,
     InputField,
@@ -39,6 +40,7 @@ import {
 } from '../templates/helpers';
 import axios from '../lib/axios';
 import styles from "../lib/styles.scss";
+import campaignsStyles from "./styles.scss";
 import {getUrl} from "../lib/urls";
 import {
     campaignOverridables,
@@ -100,6 +102,8 @@ export default class CUD extends Component {
             sendConfiguration: null
         };
 
+        this.nextListEntryId = 0;
+
         this.initForm({
             onChange: {
                 send_configuration: ::this.onSendConfigurationChanged
@@ -116,6 +120,12 @@ export default class CUD extends Component {
         type: PropTypes.number
     }
 
+    getNextListEntryId() {
+        const id = this.nextListEntryId;
+        this.nextListEntryId += 1;
+        return id;
+    }
+
     onCustomTemplateTypeChanged(mutState, key, oldType, type) {
         if (type) {
             this.templateTypes[type].afterTypeChange(mutState);
@@ -156,12 +166,24 @@ export default class CUD extends Component {
                     data.data_feedUrl = data.data.feedUrl;
                 }
 
-                data.useSegmentation = !!data.segment;
-
                 for (const overridable of campaignOverridables) {
                     data[overridable + '_overriden'] = !!data[overridable + '_override'];
                 }
 
+                const lsts = [];
+                for (const lst of data.lists) {
+                    const lstUid = this.getNextListEntryId();
+
+                    const prefix = 'lists_' + lstUid + '_';
+
+                    data[prefix + 'list'] = lst.list;
+                    data[prefix + 'segment'] = lst.segment;
+                    data[prefix + 'useSegmentation'] = !!lst.segment;
+
+                    lsts.push(lstUid);
+                }
+                data.lists = lsts;
+
                 this.fetchSendConfiguration(data.send_configuration);
             });
 
@@ -172,6 +194,9 @@ export default class CUD extends Component {
                 data[overridable + '_overriden'] = false;
             }
 
+            const lstUid = this.getNextListEntryId();
+            const lstPrefix = 'lists_' + lstUid + '_';
+
             this.populateFormValues({
                 ...data,
 
@@ -179,9 +204,12 @@ export default class CUD extends Component {
 
                 name: '',
                 description: '',
-                list: null,
-                segment: null,
-                useSegmentation: false,
+
+                [lstPrefix + 'list']: null,
+                [lstPrefix + 'segment']: null,
+                [lstPrefix + 'useSegmentation']: false,
+                lists: [lstUid],
+
                 send_configuration: null,
                 namespace: mailtrainConfig.user.namespace,
 
@@ -227,14 +255,6 @@ export default class CUD extends Component {
             state.setIn(['name', 'error'], t('Name must not be empty'));
         }
 
-        if (!state.getIn(['list', 'value'])) {
-            state.setIn(['list', 'error'], t('List must be selected'));
-        }
-
-        if (state.getIn(['useSegmentation', 'value']) && !state.getIn(['segment', 'value'])) {
-            state.setIn(['segment', 'error'], t('Segment must be selected'));
-        }
-
         if (!state.getIn(['send_configuration', 'value'])) {
             state.setIn(['send_configuration', 'error'], t('Send configuration must be selected'));
         }
@@ -281,10 +301,25 @@ export default class CUD extends Component {
             }
         }
 
+        for (const lstUid of state.getIn(['lists', 'value'])) {
+            const prefix = 'lists_' + lstUid + '_';
+
+            if (!state.getIn([prefix + 'list', 'value'])) {
+                state.setIn([prefix + 'list', 'error'], t('List must be selected'));
+            }
+
+            if (campaignTypeKey === CampaignType.REGULAR || campaignTypeKey === CampaignType.RSS) {
+                if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
+                    state.setIn([prefix + 'segment', 'error'], t('Segment must be selected'));
+                }
+            }
+        }
+
         validateNamespace(t, state);
     }
 
     async submitHandler() {
+        const isEdit = !!this.props.entity;
         const t = this.props.t;
 
         let sendMethod, url;
@@ -302,11 +337,6 @@ export default class CUD extends Component {
         const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
             data.source = Number.parseInt(data.source);
 
-            if (!data.useSegmentation) {
-                data.segment = null;
-            }
-            delete data.useSegmentation;
-
             data.data = {};
             if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
                 data.data.sourceTemplate = data.data_sourceTemplate;
@@ -316,7 +346,7 @@ export default class CUD extends Component {
                 data.data.sourceCampaign = data.data_sourceCampaign;
             }
 
-            if (data.source === CampaignSource.CUSTOM) {
+            if (!isEdit && data.source === CampaignSource.CUSTOM) {
                 this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
 
                 data.data.sourceCustom = {
@@ -342,8 +372,21 @@ export default class CUD extends Component {
                 delete data[overridable + '_overriden'];
             }
 
+            const lsts = [];
+            for (const lstUid of data.lists) {
+                const prefix = 'lists_' + lstUid + '_';
+
+                const useSegmentation = data[prefix + 'useSegmentation'] && (data.type === CampaignType.REGULAR || data.type === CampaignType.RSS);
+
+                lsts.push({
+                    list: data[prefix + 'list'],
+                    segment: useSegmentation ? data[prefix + 'segment'] : null
+                });
+            }
+            data.lists = lsts;
+
             for (const key in data) {
-                if (key.startsWith('data_')) {
+                if (key.startsWith('data_') || key.startsWith('lists_')) {
                     delete data[key];
                 }
             }
@@ -364,6 +407,47 @@ export default class CUD extends Component {
         }
     }
 
+    onAddListEntry(orderBeforeIdx) {
+        this.updateForm(mutState => {
+            const lsts = mutState.getIn(['lists', 'value']);
+            let paramId = 0;
+
+            const lstUid = this.getNextListEntryId();
+
+            const prefix = 'lists_' + lstUid + '_';
+
+            mutState.setIn([prefix + 'list', 'value'], null);
+            mutState.setIn([prefix + 'segment', 'value'], null);
+            mutState.setIn([prefix + 'useSegmentation', 'value'], false);
+
+            mutState.setIn(['lists', 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
+        });
+    }
+
+    onRemoveListEntry(lstUid) {
+        this.updateForm(mutState => {
+            const lsts = this.getFormValue('lists');
+
+            const prefix = 'lists_' + lstUid + '_';
+
+            mutState.delete(prefix + 'list');
+            mutState.delete(prefix + 'segment');
+            mutState.delete(prefix + 'useSegmentation');
+
+            mutState.setIn(['lists', 'value'], lsts.filter(val => val !== lstUid));
+        });
+    }
+
+    onListEntryMoveUp(orderIdx) {
+        const lsts = this.getFormValue('lists');
+        this.updateFormValue('lists', [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
+    }
+
+    onListEntryMoveDown(orderIdx) {
+        const lsts = this.getFormValue('lists');
+        this.updateFormValue('lists', [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
+    }
+
     render() {
         const t = this.props.t;
         const isEdit = !!this.props.entity;
@@ -390,6 +474,81 @@ export default class CUD extends Component {
             { data: 1, title: t('Name') }
         ];
 
+        const lstsEditEntries = [];
+        const lsts = this.getFormValue('lists') || [];
+        let lstOrderIdx = 0;
+        for (const lstUid of lsts) {
+            const prefix = 'lists_' + lstUid + '_';
+            const lstOrderIdxClosure = lstOrderIdx;
+
+            const selectedList = this.getFormValue(prefix + 'list');
+
+            lstsEditEntries.push(
+                
+                    
+                        {lsts.length > 1 &&
+                        
+                    
+                        
+
+                        {(campaignTypeKey === CampaignType.REGULAR || campaignTypeKey === CampaignType.RSS) &&
+                            
+                                
+                                {selectedList && this.getFormValue(prefix + 'useSegmentation') &&
+                                    
+                                }
+                            
+                        }
+                    
+                
                 {canDelete &&
@@ -520,12 +681,7 @@ export default class CUD extends Component {
 
                     
 
-                    
-
-                    
-                    {this.getFormValue('useSegmentation') &&
-                        
-                    }
+                    {lstsEdit}
 
                     
 
diff --git a/client/src/campaigns/styles.scss b/client/src/campaigns/styles.scss
new file mode 100644
index 00000000..5c314cfc
--- /dev/null
+++ b/client/src/campaigns/styles.scss
@@ -0,0 +1,38 @@
+.entry {
+  border-bottom: 1px solid #e5e5e5;
+  margin-bottom: 15px;
+  min-height: 91px;
+  position: relative;
+
+  &:last-child {
+    border-bottom: 0px none;
+    margin-bottom: 0px;
+  }
+
+  .entryButtons {
+    position: absolute;
+    top: -8px;
+    right: -8px;
+    width: 19px;
+
+    button {
+      padding: 2px 3px;
+      font-size: 11px;
+      display: block;
+      margin-bottom: 2px;
+    }
+
+    button:last-child {
+      margin-bottom: 0px;
+    }
+  }
+
+  &.entryWithButtons > .entryContent {
+    margin-right: 26px;
+  }
+}
+
+.newEntry{
+  text-align: right;
+  margin-bottom: 15px;
+}
diff --git a/client/src/campaigns/triggers/CUD.js b/client/src/campaigns/triggers/CUD.js
index 462eff24..9168a60b 100644
--- a/client/src/campaigns/triggers/CUD.js
+++ b/client/src/campaigns/triggers/CUD.js
@@ -191,6 +191,8 @@ export default class CUD extends Component {
             { data: 5, title: t('Namespace') }
         ];
 
+        const campaignLists = this.props.campaign.lists.map(x => x.list).join(';');
+
         return (
             
                 {isEdit &&
@@ -221,7 +223,7 @@ export default class CUD extends Component {
                     {entityKey === Entity.CAMPAIGN && 
}
 
                     {entityKey === Entity.CAMPAIGN &&
-                        
+                        
                     }
 
                     
diff --git a/client/src/campaigns/triggers/List.js b/client/src/campaigns/triggers/List.js
index f40c319e..ef5913bc 100644
--- a/client/src/campaigns/triggers/List.js
+++ b/client/src/campaigns/triggers/List.js
@@ -44,11 +44,10 @@ export default class List extends Component {
         const columns = [
             { data: 1, title: t('Name') },
             { data: 2, title: t('Description') },
-            { data: 3, title: t('List') },
-            { data: 4, title: t('Entity'), render: data => this.entityLabels[data], searchable: false },
-            { data: 5, title: t('Event'), render: (data, cmd, rowData) => this.eventLabels[rowData[4]][data], searchable: false },
-            { data: 6, title: t('Days after'), render: data => Math.round(data / (3600 * 24)) },
-            { data: 7, title: t('Enabled'), render: data => data ? t('Yes') : t('No'), searchable: false},
+            { data: 3, title: t('Entity'), render: data => this.entityLabels[data], searchable: false },
+            { data: 4, title: t('Event'), render: (data, cmd, rowData) => this.eventLabels[rowData[3]][data], searchable: false },
+            { data: 5, title: t('Days after'), render: data => Math.round(data / (3600 * 24)) },
+            { data: 6, title: t('Enabled'), render: data => data ? t('Yes') : t('No'), searchable: false},
             {
                 actions: data => {
                     const actions = [];
diff --git a/config/default.yaml b/config/default.yaml
index 96239747..b7fa9f78 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -129,7 +129,6 @@ nodemailer:
 
 queue:
   # How many parallel sender processes to spawn
-  # You can use more than 1 process only if you have Redis enabled
   processes: 1
 
 cors:
diff --git a/lib/executor.js b/lib/executor.js
index dc9ace0e..894c1ccd 100644
--- a/lib/executor.js
+++ b/lib/executor.js
@@ -15,7 +15,7 @@ module.exports = {
 };
 
 function spawn(callback) {
-    log.info('Executor', 'Spawning executor process');
+    log.verbose('Executor', 'Spawning executor process');
 
     executorProcess = fork(path.join(__dirname, '..', 'services', 'executor.js'), [], {
         cwd: path.join(__dirname, '..'),
diff --git a/lib/feedcheck.js b/lib/feedcheck.js
index c48fd35a..57a24a90 100644
--- a/lib/feedcheck.js
+++ b/lib/feedcheck.js
@@ -11,7 +11,7 @@ module.exports = {
 };
 
 function spawn(callback) {
-    log.info('Feed', 'Spawning feedcheck process');
+    log.verbose('Feed', 'Spawning feedcheck process');
 
     feedcheckProcess = fork(path.join(__dirname, '..', 'services', 'feedcheck.js'), [], {
         cwd: path.join(__dirname, '..'),
diff --git a/lib/importer.js b/lib/importer.js
index a5547bec..ef59e421 100644
--- a/lib/importer.js
+++ b/lib/importer.js
@@ -15,7 +15,7 @@ module.exports = {
 };
 
 function spawn(callback) {
-    log.info('Importer', 'Spawning importer process');
+    log.verbose('Importer', 'Spawning importer process');
 
     knex.transaction(async tx => {
         await tx('imports').where('status', ImportStatus.PREP_RUNNING).update({status: ImportStatus.PREP_SCHEDULED});
diff --git a/lib/senders.js b/lib/senders.js
index 65cfe36b..564a546f 100644
--- a/lib/senders.js
+++ b/lib/senders.js
@@ -8,7 +8,7 @@ let messageTid = 0;
 let senderProcess;
 
 function spawn(callback) {
-    log.info('Senders', 'Spawning master sender process');
+    log.verbose('Senders', 'Spawning master sender process');
 
     senderProcess = fork(path.join(__dirname, '..', 'services', 'sender-master.js'), [], {
         cwd: path.join(__dirname, '..'),
diff --git a/models/campaigns.js b/models/campaigns.js
index c4666b42..ae30a3d1 100644
--- a/models/campaigns.js
+++ b/models/campaigns.js
@@ -15,7 +15,7 @@ const segments = require('./segments');
 const sendConfigurations = require('./send-configurations');
 const triggers = require('./triggers');
 
-const allowedKeysCommon = ['name', 'description', 'list', 'segment', 'namespace',
+const allowedKeysCommon = ['name', 'description', 'segment', 'namespace',
     'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url'];
 
 const allowedKeysCreate = new Set(['type', 'source', ...allowedKeysCommon]);
@@ -33,9 +33,11 @@ function hash(entity, content) {
 
     if (content === Content.ALL) {
         filteredEntity = filterObject(entity, allowedKeysUpdate);
+        filteredEntity.lists = entity.lists;
 
     } else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
         filteredEntity = filterObject(entity, allowedKeysUpdate);
+        filteredEntity.lists = entity.lists;
         filteredEntity.data = {...filteredEntity.data};
         delete filteredEntity.data.sourceCustom;
 
@@ -73,7 +75,7 @@ async function listWithContentDTAjax(context, params) {
     );
 }
 
-async function listOthersByListDTAjax(context, campaignId, listId, params) {
+async function listOthersWhoseListsAreIncludedDTAjax(context, campaignId, listIds, params) {
     return await dtHelpers.ajaxListWithPermissions(
         context,
         [{ entityTypeId: 'campaign', requiredOperations: ['view'] }],
@@ -81,17 +83,44 @@ async function listOthersByListDTAjax(context, campaignId, listId, params) {
         builder => builder.from('campaigns')
             .innerJoin('namespaces', 'namespaces.id', 'campaigns.namespace')
             .whereNot('campaigns.id', campaignId)
-            .where('campaigns.list', listId),
+            .whereNotExists(qry => qry.from('campaign_lists').whereRaw('campaign_lists.campaign = campaigns.id').whereNotIn('campaign_lists.list', listIds)),
         ['campaigns.id', 'campaigns.name', 'campaigns.description', 'campaigns.type', 'campaigns.created', 'namespaces.name']
     );
 }
 
-async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) {
-    await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view');
-    let entity = await tx('campaigns').where('id', id).first();
+async function rawGetByIdTx(tx, id) {
+    const entity = await tx('campaigns').where('campaigns.id', id)
+        .innerJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
+        .groupBy('campaigns.id')
+        .select([
+            'campaigns.id', 'campaigns.name', 'campaigns.description', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
+            'campaigns.send_configuration', 'campaigns.from_name_override', 'campaigns.from_email_override', 'campaigns.reply_to_override', 'campaigns.subject_override',
+            'campaigns.data', 'campaigns.click_tracking_disabled', 'campaigns.open_tracking_disabled', 'campaigns.unsubscribe_url',
+            knex.raw(`GROUP_CONCAT(CONCAT_WS(\':\', campaign_lists.list, campaign_lists.segment) ORDER BY campaign_lists.id SEPARATOR \';\') as lists`)
+        ])
+        .first();
+
+    if (!entity) {
+        throw new interoperableErrors.NotFoundError();
+    }
+
+    entity.lists = entity.lists.split(';').map(x => {
+        const entries = x.split(':');
+        const list = Number.parseInt(entries[0]);
+        const segment = entries[1] ? Number.parseInt(entries[1]) : null;
+        return {list, segment};
+    });
 
     entity.data = JSON.parse(entity.data);
 
+    return entity;
+}
+
+async function getByIdTx(tx, context, id, withPermissions = true, content = Content.ALL) {
+    await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'view');
+
+    let entity = await rawGetByIdTx(tx, id);
+
     if (content === Content.WITHOUT_SOURCE_CUSTOM) {
         delete entity.data.sourceCustom;
 
@@ -135,11 +164,13 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
             enforce(entity.source >= CampaignSource.MIN && entity.source <= CampaignSource.MAX, 'Unknown campaign source');
         }
 
-        await shares.enforceEntityPermissionTx(tx, context, 'list', entity.list, 'view');
+        for (const lstSeg of entity.lists) {
+            await shares.enforceEntityPermissionTx(tx, context, 'list', lstSeg.list, 'view');
 
-        if (entity.segment) {
-            // Check that the segment under the list exists
-            await segments.getByIdTx(tx, context, entity.list, entity.segment);
+            if (lstSeg.segment) {
+                // Check that the segment under the list exists
+                await segments.getByIdTx(tx, context, lstSeg.list, lstSeg.segment);
+            }
         }
 
         await shares.enforceEntityPermissionTx(tx, context, 'sendConfiguration', entity.send_configuration, 'viewPublic');
@@ -218,6 +249,8 @@ async function _createTx(tx, context, entity, content) {
         const ids = await tx('campaigns').insert(filteredEntity);
         const id = ids[0];
 
+        await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: id, ...x})));
+
         await knex.schema.raw('CREATE TABLE `campaign__' + id + '` (\n' +
             '  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,\n' +
             '  `list` int(10) unsigned NOT NULL,\n' +
@@ -279,12 +312,8 @@ async function updateWithConsistencyCheck(context, entity, content) {
     await knex.transaction(async tx => {
         await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
 
-        const existing = await tx('campaigns').where('id', entity.id).first();
-        if (!existing) {
-            throw new interoperableErrors.NotFoundError();
-        }
+        const existing = await rawGetByIdTx(tx, entity.id);
 
-        existing.data = JSON.parse(existing.data);
         const existingHash = hash(existing, content);
         if (existingHash !== entity.originalHash) {
             throw new interoperableErrors.ChangedError();
@@ -311,6 +340,9 @@ async function updateWithConsistencyCheck(context, entity, content) {
         filteredEntity.data = JSON.stringify(filteredEntity.data);
         await tx('campaigns').where('id', entity.id).update(filteredEntity);
 
+        await tx('campaign_lists').where('campaign', entity.id).del();
+        await tx('campaign_lists').insert(entity.lists.map(x => ({campaign: entity.id, ...x})));
+
         await shares.rebuildPermissionsTx(tx, { entityTypeId: 'campaign', entityId: entity.id });
     });
 }
@@ -345,7 +377,7 @@ Object.assign(module.exports, {
     hash,
     listDTAjax,
     listWithContentDTAjax,
-    listOthersByListDTAjax,
+    listOthersWhoseListsAreIncludedDTAjax,
     getByIdTx,
     getById,
     create,
diff --git a/models/lists.js b/models/lists.js
index 9d136502..7ed9e919 100644
--- a/models/lists.js
+++ b/models/lists.js
@@ -34,10 +34,11 @@ async function listDTAjax(context, params) {
         ['lists.id', 'lists.name', 'lists.cid', 'lists.subscribers', 'lists.description', 'namespaces.name',
             { query: builder =>
                 builder.from('campaigns')
-                    .whereRaw('campaigns.list = lists.id')
-                    .innerJoin(campaignEntityType.permissionsTable, 'campaigns.id', `${campaignEntityType.permissionsTable}.entity`)
-                    .where(`${campaignEntityType.permissionsTable}.operation`, 'viewTriggers')
+                    .innerJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
                     .innerJoin('triggers', 'campaigns.id', 'triggers.campaign')
+                    .innerJoin(campaignEntityType.permissionsTable, 'campaigns.id', `${campaignEntityType.permissionsTable}.entity`)
+                    .whereRaw('campaign_lists.list = lists.id')
+                    .where(`${campaignEntityType.permissionsTable}.operation`, 'viewTriggers')
                     .count()
             }
         ]
diff --git a/models/triggers.js b/models/triggers.js
index bf1cc335..4434651b 100644
--- a/models/triggers.js
+++ b/models/triggers.js
@@ -35,9 +35,8 @@ async function listByCampaignDTAjax(context, campaignId, params) {
             builder => builder
                 .from('triggers')
                 .innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
-                .innerJoin('lists', 'lists.id', 'campaigns.list')
                 .where('triggers.campaign', campaignId),
-            [ 'triggers.id', 'triggers.name', 'triggers.description', 'lists.name', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled' ]
+            [ 'triggers.id', 'triggers.name', 'triggers.description', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled' ]
         );
     });
 }
@@ -50,7 +49,8 @@ async function listByListDTAjax(context, listId, params) {
         builder => builder
             .from('triggers')
             .innerJoin('campaigns', 'campaigns.id', 'triggers.campaign')
-            .where('campaigns.list', listId),
+            .innerJoin('campaign_lists', 'campaign_lists.campaign', 'campaigns.id')
+            .where('campaign_lists.list', listId),
         [ 'triggers.id', 'triggers.name', 'triggers.description', 'campaigns.name', 'triggers.entity', 'triggers.event', 'triggers.seconds_after', 'triggers.enabled', 'triggers.campaign' ]
     );
 }
@@ -128,7 +128,7 @@ async function remove(context, campaignId, id) {
 }
 
 async function removeAllByCampaignIdTx(tx, context, campaignId) {
-    const entities = await tx('triggers').where('list', campaignId).select(['id']);
+    const entities = await tx('triggers').where('campaign', campaignId).select(['id']);
     for (const entity of entities) {
         await removeTx(tx, context, campaignId, entity.id);
     }
diff --git a/routes/rest/campaigns.js b/routes/rest/campaigns.js
index 0d4f6d5e..cb1791ce 100644
--- a/routes/rest/campaigns.js
+++ b/routes/rest/campaigns.js
@@ -14,8 +14,8 @@ router.postAsync('/campaigns-with-content-table', passport.loggedIn, async (req,
     return res.json(await campaigns.listWithContentDTAjax(req.context, req.body));
 });
 
-router.postAsync('/campaigns-others-by-list-table/:campaignId/:listId', passport.loggedIn, async (req, res) => {
-    return res.json(await campaigns.listOthersByListDTAjax(req.context, req.params.campaignId, req.params.listId, req.body));
+router.postAsync('/campaigns-others-by-list-table/:campaignId/:listIds', passport.loggedIn, async (req, res) => {
+    return res.json(await campaigns.listOthersWhoseListsAreIncludedDTAjax(req.context, req.params.campaignId, req.params.listIds.split(';'), req.body));
 });
 
 
diff --git a/services/feedcheck.js b/services/feedcheck.js
index c3ccb411..792883bf 100644
--- a/services/feedcheck.js
+++ b/services/feedcheck.js
@@ -51,20 +51,20 @@ async function run() {
 
     running = true;
 
-    let rssCampaign;
+    let rssCampaignIdRow;
 
-    while (rssCampaign = await knex('campaigns')
+    while (rssCampaignIdRow = await knex('campaigns')
         .where('type', CampaignType.RSS)
         .where('status', CampaignStatus.ACTIVE)
         .where(qry => qry.whereNull('last_check').orWhere('last_check', '<', new Date(Date.now() - feedCheckInterval)))
-        // 'SELECT `id`, `source_url`, `from`, `address`, `subject`, `list`, `segment`, `html`, `open_tracking_disabled`, `click_tracking_disabled`
+        .select('id')
         .first()) {
 
+        const rssCampaign = campaigns.getById(contextHelpers.getAdminContext(), rssCampaignIdRow.id);
+
         let checkStatus = null;
 
         try {
-            rssCampaign.data = JSON.parse(rssCampaign.data);
-
             const entries = await fetch(rssCampaign.data.feedUrl);
 
             let added = 0;
@@ -95,8 +95,7 @@ async function run() {
                             type: CampaignType.RSS_ENTRY,
                             source,
                             name: entry.title || `RSS entry ${entry.guid.substr(0, 67)}`,
-                            list: rssCampaign.list,
-                            segment: rssCampaign.segment,
+                            lists: rssCampaign.lists,
                             namespace: rssCampaign.namespace,
                             send_configuration: rssCampaign.send_configuration,
 
diff --git a/services/sender-master.js b/services/sender-master.js
index 7fe59a51..35ab82e6 100644
--- a/services/sender-master.js
+++ b/services/sender-master.js
@@ -1,18 +1,17 @@
 'use strict';
 
+const config = require('config');
 const fork = require('child_process').fork;
 const log = require('npmlog');
 const path = require('path');
+const knex = require('../lib/knex');
 
 let messageTid = 0;
 let workerProcesses = new Map();
 
-const numOfWorkerProcesses = 5;
-
 let running = false;
 
 /*
-const knex = require('../lib/knex');
 const path = require('path');
 const log = require('npmlog');
 const fsExtra = require('fs-extra-promise');
@@ -28,9 +27,16 @@ const shares = require('../models/shares');
 const _ = require('../lib/translate')._;
 */
 
+
+async function processCampaign(campaignId) {
+    const campaignSubscribersTable = 'campaign__' + campaignId;
+
+
+}
+
 async function spawnWorker(workerId) {
     return await new Promise((resolve, reject) => {
-        log.info('Senders', `Spawning worker process ${workerId}`);
+        log.verbose('Senders', `Spawning worker process ${workerId}`);
 
         const senderProcess = fork(path.join(__dirname, 'sender-worker.js'), [workerId], {
             cwd: path.join(__dirname, '..'),
@@ -79,7 +85,7 @@ function sendToWorker(workerId, msgType, data) {
 async function init() {
     const spawnWorkerFutures = [];
     let workerId;
-    for (workerId = 0; workerId < numOfWorkerProcesses; workerId++) {
+    for (workerId = 0; workerId < config.queue.processes; workerId++) {
         spawnWorkerFutures.push(spawnWorker(workerId));
     }
 
diff --git a/setup/knex/migrations/20170506102634_v1_to_v2.js b/setup/knex/migrations/20170506102634_v1_to_v2.js
index c021b018..71b15a09 100644
--- a/setup/knex/migrations/20170506102634_v1_to_v2.js
+++ b/setup/knex/migrations/20170506102634_v1_to_v2.js
@@ -841,8 +841,8 @@ async function migrateCampaigns(knex) {
     X  | parent                  | int(10) unsigned    | YES  | MUL | NULL              |                |
     OK  | name                    | varchar(255)        | NO   | MUL |                   |                |
     OK  | description             | text                | YES  |     | NULL              |                |
-    OK  | list                    | int(10) unsigned    | NO   |     | NULL              |                |
-    OK  | segment                 | int(10) unsigned    | YES  |     | NULL              |                |
+    X  | list                    | int(10) unsigned    | NO   |     | NULL              |                |
+    X  | segment                 | int(10) unsigned    | YES  |     | NULL              |                |
     X   | template                | int(10) unsigned    | NO   |     | NULL              |                |
     X   | source_url              | varchar(255)        | YES  |     | NULL              |                |
     X   | editor_name             | varchar(50)         | YES  |     |                   |                |
@@ -884,8 +884,16 @@ async function migrateCampaigns(knex) {
     scheduled - used only for campaign type NORMAL
 
     parent - discarded because it duplicates the info in table `rss`. `rss` can be used to establish a db link between RSS campaign and its entries
+    list, segment - held in campaign_lists table
      */
 
+    await knex.schema.createTable('campaign_lists', table => {
+        table.increments('id').primary();
+        table.integer('campaign').unsigned().notNullable().references('campaigns.id').onDelete('CASCADE');
+        table.integer('list').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
+        table.integer('segment').unsigned().references('segments.id').onDelete('CASCADE');
+    });
+
     await knex.schema.table('campaigns', table => {
         table.text('data', 'longtext');
         table.integer('source').unsigned().notNullable();
@@ -940,10 +948,18 @@ async function migrateCampaigns(knex) {
 
         campaign.data = JSON.stringify(data);
 
+        await knex('campaign_lists').insert({
+            campaign: campaign.id,
+            list: campaign.list,
+            segment: campaign.segment || null
+        });
+
         await knex('campaigns').where('id', campaign.id).update(campaign);
     }
 
     await knex.schema.table('campaigns', table => {
+        table.dropColumn('list');
+        table.dropColumn('segment');
         table.dropColumn('template');
         table.dropColumn('source_url');
         table.dropColumn('editor_name');
@@ -1001,9 +1017,17 @@ async function migrateTriggers(knex) {
     const triggers = await knex('triggers');
 
     for (const trigger of triggers) {
-        const campaign = await knex('campaigns').where('id', trigger.campaign).first();
+        const campaign = await knex('campaigns')
+            .innerJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
+            .groupBy('campaigns.id')
+            .select(
+                knex.raw(`GROUP_CONCAT(campaign_lists.list SEPARATOR \';\') as lists`)
+            )
+            .where('id', trigger.campaign).first();
 
-        enforce(campaign.list === trigger.list, 'The list of trigger and campaign have to be the same.');
+        campaign.lists = campaign.lists.split(';').map(x => Number.parseInt(x));
+
+        enforce(campaign.lists.includes(trigger.list), 'The list of trigger and campaign have to be the same.');
 
         enforce(trigger.entity in TriggerEntityVals);
         enforce(trigger.event in TriggerEventVals[trigger.entity]);