Index: main/plugin-newsletter/cms-ribbon.xml
===================================================================
--- main/plugin-newsletter/cms-ribbon.xml	(revision 37228)
+++ main/plugin-newsletter/cms-ribbon.xml	(working copy)
@@ -57,7 +57,11 @@
 					<medium>
 						<control id="org.ametys.newsletter.Subscribers"/>
 						<control id="org.ametys.newsletter.subscribers.Export"/>
-						<control id="org.ametys.newsletter.subscribers.ImportCsv"/>
+                        <layout size="small">
+						  <control id="org.ametys.newsletter.subscribers.ImportCsv"/>
+						  <control id="org.ametys.newsletter.subscribers.AddSubscribers"/>
+						  <control id="org.ametys.newsletter.subscribers.removeSubscribers"/>
+                      </layout>
 					</medium>
 					<small/>
 				</group>
Index: main/plugin-newsletter/i18n/messages_en.xml
===================================================================
--- main/plugin-newsletter/i18n/messages_en.xml	(revision 37228)
+++ main/plugin-newsletter/i18n/messages_en.xml	(working copy)
@@ -177,7 +177,28 @@
     <message key="PLUGINS_NEWSLETTER_IMPORT_CSV_ERROR">An unexpected error has occurred : The email addresses were not imported.</message>
 	<message key="PLUGINS_NEWSLETTER_IMPORT_GET_SUBSCRIBERS_ERROR">An error occurred: the list of subscribers was not retrieved.</message>
 	
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_LABEL">Add subscribers</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_DESC">Add subscribers to the selected category.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_HINT">Insert in the following field the email addresses of the new subscribers.&lt;br/&gt;Insert one email by line or delimit them with semicolons.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_TITLE">Add subscribers</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_OK">Ok</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_CANCEL">Cancel</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_START">New subscribers added:&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_SUBSCRIBED"> email addresses were successfully added.&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_EXISTING"> email addresses were not added because they already existed.&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_ERROR"> email addresses were not added.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_TITLE">Add subscribers</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_UNKNOWN_CATEGORY">The email addresses could not be added: the specified category does not exist.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR">An unexpected error has occurred : The email addresses were not added.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_TITLE">Add subscribers</message>
 	
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_LABEL">Remove subscribers</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_DESC">Remove the selected subscribers from the current category.</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS">Remove subscribers</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_CONFIRM">Do you really want to remove the selected subscribers ?</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_ERROR">An unexpected error has occurred : The email addresses were not removed.</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_UNKNOWN_CATEGORY">The email addresses could not be removed: the specified category does not exist.</message>
+    
     <!-- +
          | AUTOMATIC NEWSLETTERS
          + -->
@@ -203,6 +224,12 @@
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_LABEL">Newsletter</message>
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_DESC">Newsletter contents and categories management</message>
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_FOOTER">See help for details</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_1">Content </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_2"> - </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_3"> on </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_RESULTS_MAX"> maximum</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_NO_RESULT">No subscriber found for the selected category.</message>
+    
 	
 	<!-- +
 	     | SERVICES 
Index: main/plugin-newsletter/i18n/messages_fr.xml
===================================================================
--- main/plugin-newsletter/i18n/messages_fr.xml	(revision 37228)
+++ main/plugin-newsletter/i18n/messages_fr.xml	(working copy)
@@ -177,6 +177,28 @@
 	<message key="PLUGINS_NEWSLETTER_IMPORT_CSV_ERROR">Une erreur inattendue est survenue : Les adresses e-mail n'ont pas pu être importées.</message>
 	<message key="PLUGINS_NEWSLETTER_IMPORT_GET_SUBSCRIBERS_ERROR">Une erreur est survenue: la liste des abonnés n'a pu être récupérée.</message>
 	
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_LABEL">Ajouter des abonnés</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_DESC">Ajouter des abonnés à la lettre d'information de la catégorie sélectionnée.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_HINT">Saisissez ci-après les adresses email des nouveaux abonnés.&lt;br/&gt;Entrez une adresse email par ligne ou séparez-les par des points virgules.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_TITLE">Ajouter des abonnés</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_OK">Ok</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_CANCEL">Annuler</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_START">L'ajout est terminé :&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_SUBSCRIBED"> adresses e-mail ont été ajoutées avec succès.&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_EXISTING"> adresses e-mail n'ont pas été ajoutées car elles étaient déjà présentes.&lt;br/&gt;</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_ERROR"> adresses e-mail n'ont pu être ajoutées.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_TITLE">Ajouter des abonnés</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_UNKNOWN_CATEGORY">Les adresses e-mail n'ont pas pu être ajoutées : la catégorie n'existe pas.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR">Une erreur inattendue est survenue : Les adresses e-mail n'ont pas pu être ajoutées.</message>
+    <message key="PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_TITLE">Ajouter des abonnés</message>
+    
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_LABEL">Supprimer des abonnés</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_DESC">Supprimer les abonnés selectionnés de la catégorie courante.</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS">Supprimer des abonnés</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_CONFIRM">Etes-vous sûrs de vouloir supprimer ce ou ces abonnés ?</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_ERROR">Une erreur inattendue est survenue : Les adresses e-mail n'ont pas pu être supprimées.</message>
+    <message key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_UNKNOWN_CATEGORY">Les adresses e-mail n'ont pas pu être supprimées : la catégorie n'existe pas.</message>
+    
 	<!-- +
 		 | AUTOMATIC NEWSLETTERS
 		 + -->
@@ -202,6 +224,12 @@
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_LABEL">Lettre d'information</message>
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_DESC">Gestion de la lettre d'information et des catégories</message>
 	<message key="PLUGINS_NEWSLETTER_UITOOL_NEWSLETTERS_FOOTER">Voir l'aide pour plus de détails</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_1">Contenus </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_2"> - </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_3"> sur </message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_RESULTS_MAX"> maximum</message>
+    <message key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_NO_RESULT">Aucun abonné trouvé pour la catégorie sélectionnée.</message>
+    
 	
 	<!-- +
 	     | SERVICES 
Index: main/plugin-newsletter/plugin.xml
===================================================================
--- main/plugin-newsletter/plugin.xml	(revision 37228)
+++ main/plugin-newsletter/plugin.xml	(working copy)
@@ -618,8 +618,51 @@
 				</scripts>
 				<right>Plugins_Newsletter_Right_HandleSubscribers</right>	
 			</extension>	
-    	</extensions>
     	
+            <extension id="org.ametys.newsletter.subscribers.AddSubscribers"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.newsletter.ribbon.CategoryActionButton">
+                    <param name="action">org.ametys.newsletter.subscribers.AddSubscribers</param>
+                    <param name="type">newsletter-category</param>
+                    <param name="mode">read|write</param>
+                    <param name="label" i18n="true">PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_ADD_SUBSCRIBERS_DESC</param>
+                    <param name="disabled-description" i18n="true">PLUGINS_NEWSLETTER_CONTENT_CREATENEWSLETTERACTION_DISABLED</param>
+                    <param name="footer"></param>
+                    <param name="icon-small" file="true">img/subscriber/add_subscribers_16.png</param>
+                    <param name="icon-medium" file="true">img/subscriber/add_subscribers_32.png</param>
+                    <param name="icon-large" file="true">img/subscriber/add_subscribers_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/newsletter/category/CategoryActions.i18n.js</file>
+                    <file>js/org/ametys/newsletter/ribbon/CategoryActionButton.js</file>
+                </scripts>
+                <right>Plugins_Newsletter_Right_HandleSubscribers</right>   
+            </extension>  
+            
+            <extension id="org.ametys.newsletter.subscribers.removeSubscribers"
+                       point="org.ametys.cms.workspace.ribbon.RibbonControlsManager" 
+                       class="org.ametys.runtime.ui.impl.StaticContextualClientSideElement">
+                <action class="org.ametys.ribbon.control.button.ActionSelectionButton">
+                    <param name="action">org.ametys.newsletter.subscribers.RemoveSubscribers.act</param>
+                    <param name="target-type">^newsletter-category$</param>
+                    <param name="subtarget-type">^subscriber$</param>
+                    
+                    <param name="label" i18n="true">PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_LABEL</param>
+                    <param name="default-description" i18n="true">PLUGINS_NEWSLETTER_UITOOL_SUBSCRIBERS_REMOVE_SUBSCRIBERS_DESC</param>
+                    
+                    <param name="icon-small" file="true">img/subscriber/remove_subscribers_16.png</param>
+                    <param name="icon-medium" file="true">img/subscriber/remove_subscribers_32.png</param>
+                    <param name="icon-large" file="true">img/subscriber/remove_subscribers_48.png</param>
+                </action>
+                <scripts>
+                    <file>js/org/ametys/newsletter/category/CategoryActions.i18n.js</file>
+                </scripts>
+                <right>Plugins_Newsletter_Right_HandleSubscribers</right>   
+            </extension>  
+        </extensions>
+        
     	<components>
 			<component role="org.ametys.plugins.newsletter.daos.SubscribersDAO" 
 			           class="org.ametys.plugins.newsletter.daos.SubscribersDAO" />
Index: main/plugin-newsletter/resources/js/org/ametys/newsletter/category/CategoryActions.i18n.js
===================================================================
--- main/plugin-newsletter/resources/js/org/ametys/newsletter/category/CategoryActions.i18n.js	(revision 37228)
+++ main/plugin-newsletter/resources/js/org/ametys/newsletter/category/CategoryActions.i18n.js	(working copy)
@@ -18,6 +18,8 @@
 Ext.namespace('org.ametys.newsletter.category.Delete');
 Ext.namespace('org.ametys.newsletter.subscribers.Export');
 Ext.namespace('org.ametys.newsletter.subscribers.ImportCsv');
+Ext.namespace('org.ametys.newsletter.subscribers.AddSubscribers');
+Ext.namespace('org.ametys.newsletter.subscribers.RemoveSubscribers');
 
 /**
  * Create a new category
@@ -593,3 +595,236 @@
         icon: Ext.MessageBox.ERROR
     });
 }
+
+/** ------------------------------------ */
+/** ------------------------------------ */
+/** ------------------------------------ */
+org.ametys.newsletter.subscribers.AddSubscribers._pluginName;
+org.ametys.newsletter.subscribers.AddSubscribers._dialog;
+
+org.ametys.newsletter.subscribers.AddSubscribers = function(id, config, pluginName)
+{
+    var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
+    var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) { return target.getType() == 'newsletter-category' });
+    
+    if (target == null)
+    {
+        return;
+    }
+    
+    var categoryId = target.getParameters().id;
+    
+    org.ametys.newsletter.subscribers.AddSubscribers._pluginName = pluginName;
+    
+    if (org.ametys.newsletter.subscribers.AddSubscribers._dialog == null)
+    {
+        org.ametys.newsletter.subscribers.AddSubscribers._dialog = org.ametys.newsletter.subscribers.AddSubscribers._createDialogBox();
+    }
+    
+    org.ametys.newsletter.subscribers.AddSubscribers._dialog.show();
+    org.ametys.newsletter.subscribers.AddSubscribers._initForm (categoryId);
+}
+
+org.ametys.newsletter.subscribers.AddSubscribers._initForm = function (categoryId)
+{
+    var form = org.ametys.newsletter.subscribers.AddSubscribers._dialog.items.itemAt(0).getForm();
+    form.findField('categoryId').setValue(categoryId);
+    form.findField('emailsList').reset();
+}
+
+org.ametys.newsletter.subscribers.AddSubscribers._createDialogBox = function()
+{
+    var pluginName = org.ametys.newsletter.subscribers.AddSubscribers._pluginName;
+    
+    var formPanel = new Ext.form.FormPanel( {
+        bodyStyle: 'padding:5px',
+        fileUpload: true,
+        
+        defaultType: 'textfield',
+        
+        autoScroll: true,
+        items: [
+            new org.ametys.HtmlContainer({
+                html: "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_HINT'/>",
+                cls: 'dialog-text-hint'
+            }),
+            new Ext.form.Hidden({
+                id: 'categoryId',
+                name: 'categoryId',
+                value: ''
+            }),
+            new org.ametys.form.TextAreaField({
+                id: 'emailsList',
+                name: 'emailsList',
+                width: 380,
+                height: 60,
+                hideLabel: true,
+                style:{
+                    marginLeft: '10px',
+                },
+                allowBlank: false
+            })
+        ]
+    });
+    
+    var dialog = new org.ametys.DialogBox({
+        layout: 'fit',
+        
+        title: "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_TITLE'/>",
+        icon: getPluginResourcesUrl(pluginName) + '/img/subscriber/add_subscribers_16.png',
+        
+        width: 450,
+        height: 200,
+        autoScroll: false,
+        
+        items: [formPanel],
+        
+        closeAction: 'hide',
+        buttons: [{
+            text: "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_OK'/>",
+            handler: org.ametys.newsletter.subscribers.AddSubscribers._ok
+        }, {
+            text: "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_DIALOG_BUTTON_CANCEL'/>",
+            handler: org.ametys.newsletter.subscribers.AddSubscribers._cancel
+        }]        
+    });
+    
+    return dialog;
+}
+
+org.ametys.newsletter.subscribers.AddSubscribers._ok = function(button, event)
+{
+    var form = org.ametys.newsletter.subscribers.AddSubscribers._dialog.items.itemAt(0).getForm();
+    
+    if (form.isValid())
+    {
+        var params = form.getValues();
+        params.siteName = context.parameters.siteName;
+        
+        var serverMessage = new org.ametys.servercomm.ServerMessage(org.ametys.newsletter.subscribers.AddSubscribers._pluginName, '/subscribers/add', params, org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, org.ametys.newsletter.subscribers.AddSubscribers.callback, this);
+        org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+    }
+}
+
+org.ametys.newsletter.subscribers.AddSubscribers._cancel = function(button, event)
+{
+    org.ametys.newsletter.subscribers.AddSubscribers._dialog.hide();
+}
+
+org.ametys.newsletter.subscribers.AddSubscribers.callback = function(response)
+{
+    var responseNode = response.selectSingleNode("ActionResult");
+
+    var message = '';
+    var messageTitle = '';
+
+    if (responseNode.selectSingleNode("success")[org.ametys.servercomm.ServerComm.xmlTextContent] == "true")
+    {
+        message += "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_START'/>";
+        
+        message += responseNode.selectSingleNode("subscribedCount")[org.ametys.servercomm.ServerComm.xmlTextContent] + "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_SUBSCRIBED'/>";
+        
+        if (responseNode.selectSingleNode("existingCount")[org.ametys.servercomm.ServerComm.xmlTextContent] > 0)
+        {
+            message += responseNode.selectSingleNode("existingCount")[org.ametys.servercomm.ServerComm.xmlTextContent] + "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_EXISTING'/>";
+        }
+        
+        if (responseNode.selectSingleNode("errorCount")[org.ametys.servercomm.ServerComm.xmlTextContent] > 0)
+        {
+            message += responseNode.selectSingleNode("errorCount")[org.ametys.servercomm.ServerComm.xmlTextContent] + "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_ERROR'/>";
+        }
+        
+        messageTitle = "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_SUCCESS_TITLE'/>";
+    }
+    else
+    {
+        if (responseNode.selectSingleNode("error")[org.ametys.servercomm.ServerComm.xmlTextContent] == 'unknown-category')
+        {
+            message += "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_UNKNOWN_CATEGORY'/>";
+        }
+        else
+        {
+            message += "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR'/>";
+        }
+        
+        messageTitle = "<i18n:text i18n:key='PLUGINS_NEWSLETTER_ADD_SUBSCRIBERS_ERROR_TITLE'/>";
+    }
+
+    Ext.Msg.show({
+        title: messageTitle,
+        msg: message,
+        buttons: Ext.Msg.OK,
+        icon: Ext.MessageBox.INFO
+    });
+
+    if (responseNode.selectSingleNode("success")[org.ametys.servercomm.ServerComm.xmlTextContent] == "true")
+    {
+        org.ametys.newsletter.subscribers.AddSubscribers._dialog.hide();
+        
+        var target = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("newsletter-category", {'id': responseNode.selectSingleNode("categoryId")[org.ametys.servercomm.ServerComm.xmlTextContent]});
+        var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage("subscriberAdded", null, [target]);
+        org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+    }
+}
+
+/** ------------------------------------ */
+/** ------------------------------------ */
+/** ------------------------------------ */
+/**
+ * Remove subscribers from a category
+ */
+org.ametys.newsletter.subscribers.RemoveSubscribers.act = function (id, config, pluginName)
+{
+    org.ametys.newsletter.subscribers.RemoveSubscribers.pluginName = pluginName;
+    
+    Ext.Msg.confirm("<i18n:text i18n:key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS"/>", 
+            "<i18n:text i18n:key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_CONFIRM"/>", 
+            org.ametys.newsletter.subscribers.RemoveSubscribers._doAction, 
+            this
+    );
+}
+
+org.ametys.newsletter.subscribers.RemoveSubscribers._doAction = function(button)
+{
+    if (button == 'yes')
+    {
+        var targets = org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets();
+        var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) { return target.getType() == "newsletter-category" });
+        
+        if (target == null)
+            return; 
+        
+        var emails = [];
+        for (var i=0; target._subTargets.length > i; i++)
+        {
+            emails.push(target._subTargets[i].getParameters().email);
+        }
+        
+        var params = {'categoryId':  target.getParameters().id, 'emails': emails, siteName: context.parameters.siteName}; 
+        var serverMessage = new org.ametys.servercomm.ServerMessage(org.ametys.newsletter.subscribers.RemoveSubscribers.pluginName, '/subscribers/remove', params, org.ametys.servercomm.ServerComm.PRIORITY_MAJOR, org.ametys.newsletter.subscribers.RemoveSubscribers.callback, this, [target]);
+        org.ametys.servercomm.ServerComm.getInstance().send(serverMessage);
+    }
+}
+
+org.ametys.newsletter.subscribers.RemoveSubscribers.callback = function (response, args)
+{
+    if (org.ametys.servercomm.ServerComm.handleBadResponse("<i18n:text i18n:key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_ERROR"/>", response, "org.ametys.newsletter.subscribers.RemoveSubscribers"))
+    {
+        return;
+    }
+    
+    var msg = response.selectSingleNode("ActionResult/message") != null ? response.selectSingleNode("ActionResult/message")[org.ametys.servercomm.ServerComm.xmlTextContent] : '';
+    if (msg == "unknown-category")
+    {
+        Ext.Msg.show({
+            title: "<i18n:text i18n:key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_ERROR"/>",
+            msg: "<i18n:text i18n:key="PLUGINS_NEWSLETTER_HANDLE_REMOVE_SUBSCRIBERS_UNKNOWN_CATEGORY"/>",
+            buttons: Ext.Msg.OK,
+            icon: Ext.MessageBox.ERROR
+        });
+        return;
+    }
+    
+    var event = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage("subscriberRemoved", null, [args[0]]);
+    org.ametys.messagebus.MessageBus.getInstance().fireMessages([event]);
+}
Index: main/plugin-newsletter/resources/js/org/ametys/newsletter/tool/SubscribersTool.i18n.js
===================================================================
--- main/plugin-newsletter/resources/js/org/ametys/newsletter/tool/SubscribersTool.i18n.js	(revision 37228)
+++ main/plugin-newsletter/resources/js/org/ametys/newsletter/tool/SubscribersTool.i18n.js	(working copy)
@@ -29,6 +29,9 @@
 
 Ext.extend(org.ametys.newsletter.tool.SubscribersTool, org.ametys.uitool.tool.Tool, {});
 
+//default pagination size
+org.ametys.newsletter.tool.SubscribersTool.prototype.PAGE_SIZE = 100;
+
 org.ametys.newsletter.tool.SubscribersTool.prototype.createPanel = function ()
 {
 	// Data store
@@ -48,9 +51,20 @@
 		         {name: 'subscribedAt', type:'date', dateFormat:'c', mapping: 'subscribedAt'}
 		],
 		
-		sortInfo : {field: 'email', direction: "ASC"}
+		sortInfo : {field: 'email', direction: "ASC"},
+		
+		totalProperty: '@total' 
 	});
 	
+    var pagingBar = new Ext.PagingToolbar({
+         pageSize: this.PAGE_SIZE,
+         store: this._store,
+         displayInfo: true,
+         displayMsg: "<i18n:text i18n:key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_1" i18n:catalogue="plugin.newsletter"/>{0}<i18n:text i18n:key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_2" i18n:catalogue="plugin.newsletter"/>{1}<i18n:text i18n:key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_CONTENT_RESULT_3" i18n:catalogue="plugin.newsletter"/>{2}",
+         emptyMsg: "<i18n:text i18n:key="PLUGINS_NEWSLETTER_UITOOL_SEARCH_NO_RESULT" i18n:catalogue="plugin.newsletter"/>"
+     });
+
+	
 	this._grid = new Ext.grid.GridPanel({
 		region: 'center',
 		
@@ -60,7 +74,10 @@
 	        {header: "<i18n:text i18n:key="PLUGINS_NEWSLETTER_SUBSCRIBERS_COLUMN_SUBSCRIBED_AT"/>", width: 200, sortable: true, dataIndex: 'subscribedAt', renderer: this.renderDate}
 	    ],
 	    
-        cls: 'mask-below-menu'
+	    // paging bar on the bottom
+	    bbar: pagingBar,
+
+	    cls: 'mask-below-menu'
 	});
 	this._grid.getSelectionModel().on('selectionchange', this._onSelectionChange, this);
 	
@@ -84,6 +101,8 @@
 		}
 	});
 	
+	this._store.addListener('beforeload', this._onBeforeLoad, this);
+	
 	this._reloadStore ();
 	
 	return this._mainPanel;
@@ -104,7 +123,12 @@
  */
 org.ametys.newsletter.tool.SubscribersTool.prototype._reloadStore = function (categoryID)
 {
-	this._store.load({params: {'categoryID': this._currentCategory}, callback: this._reloadCb, scope: this});
+	this._store.load({params: {start: 0, limit: this.PAGE_SIZE}, callback: this._reloadCb, scope: this});
+}
+
+org.ametys.newsletter.tool.SubscribersTool.prototype._onBeforeLoad = function (store, node)
+{
+    store.baseParams.categoryID = this._currentCategory;
 }
 
 org.ametys.newsletter.tool.SubscribersTool.prototype._reloadCb = function (records, options, success)
@@ -214,6 +238,16 @@
 				this.outOfDate(true);
 			}
 		}
+		else if (messages[i].getType().getName() == "subscriberRemoved")
+		{
+		    var targets = messages[i].getTargets();
+		    var target = org.ametys.messagebus.message.MessageTargetHelper.findFirst(targets, function(target) { return target.getType() == "newsletter-category";});
+		    
+		    if (target != null &amp;&amp; this._currentCategory == target.getParameters().id )
+		    {
+		        this.outOfDate(true);
+		    }
+		}
 	}
 }
 
Index: main/plugin-newsletter/sitemap.xmap
===================================================================
--- main/plugin-newsletter/sitemap.xmap	(revision 37228)
+++ main/plugin-newsletter/sitemap.xmap	(working copy)
@@ -41,6 +41,8 @@
 			<map:action name="subscribe" src="org.ametys.plugins.newsletter.subscribe.SubscribeAction"/>
 			<map:action name="unsubscribe" src="org.ametys.plugins.newsletter.subscribe.UnsubscribeAction"/>
 			<map:action name="import-subscribers" src="org.ametys.plugins.newsletter.subscribe.ImportSubscribersAction" logger="org.ametys.plugins.newsletter.subscribe.ImportSubscribersAction"/>
+			<map:action name="add-subscribers" src="org.ametys.plugins.newsletter.subscribe.AddSubscribersAction" logger="org.ametys.plugins.newsletter.subscribe.AddSubscribersAction"/>
+			<map:action name="remove-subscribers" src="org.ametys.plugins.newsletter.subscribe.RemoveSubscribersAction" logger="org.ametys.plugins.newsletter.subscribe.RemoveSubscribersAction"/>
 			
 			<map:action name="category-create" src="org.ametys.plugins.newsletter.category.CreateCategoryAction"/>
 			<map:action name="category-edit" src="org.ametys.plugins.newsletter.category.EditCategoryAction"/>
@@ -391,6 +393,29 @@
                 </map:act>
             </map:match>
 			
+            <map:match pattern="subscribers/add">
+                <map:act type="add-subscribers">
+                    <map:generate type="action-result">
+                        <map:parameter name="error" value="{error}"/>
+                        <map:parameter name="subscribedCount" value="{subscribedCount}"/>
+                        <map:parameter name="errorCount" value="{errorCount}"/>
+                        <map:parameter name="existingCount" value="{existingCount}"/>
+                        <map:parameter name="success" value="{success}"/>
+                        <map:parameter name="categoryId" value="{categoryId}"/>
+                    </map:generate>
+                    <map:serialize type="xml"/>	
+                </map:act>
+            </map:match>
+			         
+            <map:match pattern="subscribers/remove">
+                <map:act type="remove-subscribers">
+                    <map:generate type="action-result">
+                        <map:parameter name="message" value="{message}"/>
+                    </map:generate>
+                    <map:serialize type="xml"/> 
+                </map:act>
+            </map:match>
+            
     		<!-- +
     			 | CATEGORIES
     			 + -->
Index: main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/SubscribersDAO.java
===================================================================
--- main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/SubscribersDAO.java	(revision 37228)
+++ main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/SubscribersDAO.java	(working copy)
@@ -78,6 +78,54 @@
     }
     
     /**
+     * Get the subscribers to a newsletter category
+     * @param siteName The site name
+     * @param categoryId The newsletter category's id
+     * @param skip The number of results to ignore.
+     * @param max The maximum number of results to return.
+     * @return the subscribers
+     * @throws DataAccessException If an error occurred
+     */
+    public List<Subscriber> getSubscribers (String siteName, String categoryId, int skip, int max) throws DataAccessException
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("siteName", siteName);
+        params.put("category", categoryId);
+        
+        try
+        {
+            return _getSqlMapClient().queryForList("Subscribers.getSubscribersByCategory", params, skip, max);
+        }
+        catch (SQLException e)
+        {
+            throw new DataAccessException("Unable to the subsribers to the newsletter (" + params + ")", e);
+        }
+    }
+    
+    /**
+     * Get the subscribers count for a newsletter category
+     * @param siteName The site name
+     * @param categoryId The newsletter category's id
+     * @return the subscribers count
+     * @throws DataAccessException If an error occurred
+     */
+    public int getSubscribersCount (String siteName, String categoryId) throws DataAccessException
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("siteName", siteName);
+        params.put("category", categoryId);
+        
+        try
+        {
+            return (Integer) _getSqlMapClient().queryForObject("Subscribers.getSubscribersCount", params);
+        }
+        catch (SQLException e)
+        {
+            throw new DataAccessException("Unable to the subsribers to the newsletter (" + params + ")", e);
+        }
+    }
+    
+    /**
      * Get a subscriber to a newsletter category
      * @param email The subscriber email
      * @param siteName The site name
Index: main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/subscribers.xml
===================================================================
--- main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/subscribers.xml	(revision 37228)
+++ main/plugin-newsletter/src/org/ametys/plugins/newsletter/daos/subscribers.xml	(working copy)
@@ -55,6 +55,11 @@
     <include refid="getSubscribers"/>
     WHERE Site_Name = #siteName# and Email = #email#
   </select>
+  
+  <select id="getSubscribersCount" parameterClass="java.util.Map" resultClass="int">
+        SELECT count(*) FROM Newsletter_Subscribers
+        WHERE Site_Name = #siteName# and Category = #category#
+  </select>
 
   <insert id="subscribe" parameterClass="org.ametys.plugins.newsletter.daos.Subscriber">
     INSERT INTO Newsletter_Subscribers (Email, Site_Name, Category, Subscribed_At, Token)
Index: main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/AddSubscribersAction.java
===================================================================
--- main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/AddSubscribersAction.java	(revision 0)
+++ main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/AddSubscribersAction.java	(revision 0)
@@ -0,0 +1,185 @@
+/*
+ *  Copyright 2011 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.newsletter.subscribe;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.commons.lang.StringUtils;
+
+import org.ametys.plugins.newsletter.category.Category;
+import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint;
+import org.ametys.plugins.newsletter.daos.Subscriber;
+import org.ametys.plugins.newsletter.daos.SubscribersDAO;
+import org.ametys.runtime.plugins.core.sqlmap.dao.DataAccessException;
+
+/**
+ * Import subscribers from a CSV or text file.
+ */
+public class AddSubscribersAction extends ServiceableAction
+{
+    private static final Pattern __EMAIL_VALIDATOR = Pattern.compile("^([a-z0-9._-]+@[a-z0-9.-]{2,}[.][a-zA-Z0-9]{2,})?$");
+    
+    /** The subscribers DAO. */
+    protected SubscribersDAO _subscribersDao;
+    
+    /** The category provider extension point. */
+    protected CategoryProviderExtensionPoint _categoryProviderEP;
+
+    @Override
+    public void service(ServiceManager smanager) throws ServiceException
+    {
+        super.service(smanager);
+        _subscribersDao = (SubscribersDAO) smanager.lookup(SubscribersDAO.ROLE);
+        _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE);
+    }
+    
+    @Override
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {
+        Map<String, String> result = new HashMap<String, String>();
+
+        Request request = ObjectModelHelper.getRequest(objectModel);
+
+        String siteName = parameters.getParameter("siteName", request.getParameter("siteName"));
+        String categoryId = request.getParameter("categoryId");
+        String emailsList = request.getParameter("emailsList");
+        
+        // Test if the category exists.
+        Category category = _categoryProviderEP.getCategory(categoryId);
+        if (category == null)
+        {
+            return Collections.singletonMap("error", "unknown-category");
+        }
+        
+        // Extract the emails from the import file.
+        Collection<String> emails = getEmails(emailsList);
+        
+        if (!emails.isEmpty())
+        {
+            // Insert the emails.
+            insertSubscribers(emails, categoryId, siteName, result);
+        }
+        else
+        {
+            result.put("subscribedCount", "0");
+            result.put("existingCount", "0");
+            result.put("errorCount", "0");
+        }
+        
+        result.put("success", "true");
+        result.put("categoryId", categoryId);
+        
+        return result;
+    }
+    
+    /**
+     * Extract the emails from the field.
+     * @param emailsList the list of emails
+     * @return a collection of the emails.
+     */
+    protected Collection<String> getEmails(String emailsList)
+    {
+        Set<String> emails = new LinkedHashSet<String>();
+        
+        String[] part = emailsList.split("[;\n]");
+        for (String line : part)
+        {
+            String email = StringUtils.trimToEmpty(line);
+            if (__EMAIL_VALIDATOR.matcher(email.toLowerCase()).matches() && StringUtils.isNotBlank(email))
+            {
+                emails.add(email);
+            }
+            else
+            {
+                getLogger().warn("Import subscribers email: '" + email + "' is not a valid email; it will be ignored");
+            }
+        }
+        
+        return emails;
+    }
+    
+    /**
+     * Insert subscribers
+     * @param emails
+     * @param categoryId
+     * @param siteName
+     * @param result 
+     */
+    protected void insertSubscribers(Collection<String> emails, String categoryId, String siteName, Map<String, String> result)
+    {
+        int subscribedCount = 0;
+        int existingCount = 0;
+        int errorCount = 0;
+        
+        for (String email : emails)
+        {
+            try
+            {
+                if (_subscribersDao.getSubscriber(email, siteName, categoryId) == null)
+                {
+                    Subscriber subscriber = new Subscriber();
+                    subscriber.setEmail(email);
+                    subscriber.setSiteName(siteName);
+                    subscriber.setCategoryId(categoryId);
+                    subscriber.setSubscribedAt(new Date());
+                    
+                    // Generate unique token.
+                    String token = UUID.randomUUID().toString();
+                    subscriber.setToken(token);
+                    
+                    _subscribersDao.subscribe(subscriber);
+                    
+                    if (getLogger().isInfoEnabled())
+                    {
+                        getLogger().info("The user with email '" + email + "' subscribed to the newsletter with the token " + token);
+                    }
+                    
+                    subscribedCount++;
+                }
+                else
+                {
+                    existingCount++;
+                }
+            }
+            catch (DataAccessException e)
+            {
+                getLogger().error("Impossible to add as a subscriber the email " + email + " in category " + categoryId + " of site " + siteName, e);
+                errorCount++;
+            }
+        }
+        
+        result.put("subscribedCount", Integer.toString(subscribedCount));
+        result.put("existingCount", Integer.toString(existingCount));
+        result.put("errorCount", Integer.toString(errorCount));
+    }
+
+}
Index: main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/RemoveSubscribersAction.java
===================================================================
--- main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/RemoveSubscribersAction.java	(revision 0)
+++ main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/RemoveSubscribersAction.java	(revision 0)
@@ -0,0 +1,119 @@
+/*
+ *  Copyright 2011 Anyware Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.ametys.plugins.newsletter.subscribe;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.avalon.framework.parameters.Parameters;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.acting.ServiceableAction;
+import org.apache.cocoon.environment.ObjectModelHelper;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.SourceResolver;
+
+import org.ametys.plugins.newsletter.category.Category;
+import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint;
+import org.ametys.plugins.newsletter.daos.Subscriber;
+import org.ametys.plugins.newsletter.daos.SubscribersDAO;
+import org.ametys.runtime.plugins.core.sqlmap.dao.DataAccessException;
+
+/**
+ * Import subscribers from a CSV or text file.
+ */
+public class RemoveSubscribersAction extends ServiceableAction
+{
+    /** The subscribers DAO. */
+    protected SubscribersDAO _subscribersDao;
+    
+    /** The category provider extension point. */
+    protected CategoryProviderExtensionPoint _categoryProviderEP;
+
+    @Override
+    public void service(ServiceManager smanager) throws ServiceException
+    {
+        super.service(smanager);
+        _subscribersDao = (SubscribersDAO) smanager.lookup(SubscribersDAO.ROLE);
+        _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE);
+    }
+    
+    @Override
+    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
+    {
+        Map<String, String> result = new HashMap<String, String>();
+
+        Request request = ObjectModelHelper.getRequest(objectModel);
+
+        String siteName = parameters.getParameter("siteName", request.getParameter("siteName"));
+        String categoryId = request.getParameter("categoryId");
+        String[] emails = request.getParameterValues("emails");
+        
+        // Test if the category exists.
+        Category category = _categoryProviderEP.getCategory(categoryId);
+        if (category == null)
+        {
+            return Collections.singletonMap("message", "unknown-category");
+        }
+        
+        if (emails.length > 0)
+        {
+            removeSubscribers(emails, categoryId, siteName);
+        }
+        
+        return result;
+    }
+    
+    /**
+     * Remove subscribers
+     * @param emails
+     * @param categoryId
+     * @param siteName
+     */
+    protected void removeSubscribers(String[] emails, String categoryId, String siteName)
+    {
+        for (String email : emails)
+        {
+            try
+            {
+                Subscriber subscriber = _subscribersDao.getSubscriber(email, siteName, categoryId);
+                
+                if (subscriber != null)
+                {
+                    String token = subscriber.getToken();
+                    
+                    _subscribersDao.unsubscribe(token);
+                    
+                    if (getLogger().isInfoEnabled())
+                    {
+                        getLogger().info("The user with email '" + email + "' unsubscribed from the newsletter in category " + categoryId + " of site " + siteName);
+                    }
+                }
+                else
+                {
+                    getLogger().error("Impossible to find and remove from the newsletter the subscriber with email '" + email + "' in category " + categoryId + " of site " + siteName);
+                }
+            }
+            catch (DataAccessException e)
+            {
+                getLogger().error("Impossible to remove the subscriber with the email " + email + " in category " + categoryId + " of site " + siteName, e);
+            }
+        }
+    }
+
+}
Index: main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/SubscribersGenerator.java
===================================================================
--- main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/SubscribersGenerator.java	(revision 37228)
+++ main/plugin-newsletter/src/org/ametys/plugins/newsletter/subscribe/SubscribersGenerator.java	(working copy)
@@ -24,6 +24,7 @@
 import org.apache.cocoon.environment.ObjectModelHelper;
 import org.apache.cocoon.environment.Request;
 import org.apache.cocoon.generation.ServiceableGenerator;
+import org.apache.cocoon.xml.AttributesImpl;
 import org.apache.cocoon.xml.XMLUtils;
 import org.xml.sax.SAXException;
 
@@ -52,20 +53,27 @@
 
         String siteName = request.getParameter("siteName");
         String categoryId = request.getParameter("categoryID");
+        int begin = request.getParameter("start") != null ? Integer.parseInt(request.getParameter("start")) : 0; // Index of search
+        int offset = request.getParameter("limit") != null ? Integer.parseInt(request.getParameter("limit")) : Integer.MAX_VALUE; // Number of results to SAX
 
-        contentHandler.startDocument();
-        XMLUtils.startElement(contentHandler, "subscribers");
-        
         List<Subscriber> subscribers;
+        int total = 0;
         if (siteName != null && categoryId != null)
         {
-            subscribers = _subscribersDao.getSubscribers(siteName, categoryId);
+            subscribers = _subscribersDao.getSubscribers(siteName, categoryId, begin, offset);
+            total = _subscribersDao.getSubscribersCount(siteName, categoryId);
         }
         else
         {
             subscribers = _subscribersDao.getSubscribers();
+            total = subscribers.size();
         }
         
+        contentHandler.startDocument();
+        AttributesImpl attrs = new AttributesImpl();
+        attrs.addCDATAAttribute("total", String.valueOf(total));
+        XMLUtils.startElement(contentHandler, "subscribers", attrs);
+        
         for (Subscriber subscriber : subscribers)
         {
             XMLUtils.startElement(contentHandler, "subscriber");
