Bug 11039 Filter sent messages r=rkent a=starred CLOSED TREE
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Tue, 30 Dec 2014 21:53:51 +0000
changeset 22087 79d319474891f2ad519d7f60902e2f18d2797c74
parent 22086 46a69933fed73b0f60c1b88acc090671124eedb5
child 22088 2781ce3e4d00f4a49e1b28400a1981c55a661d96
push id222
push usermbanner@mozilla.com
push dateMon, 30 Mar 2015 08:24:45 +0000
reviewersrkent, starred
bugs11039
Bug 11039 Filter sent messages r=rkent a=starred CLOSED TREE
mail/locales/en-US/chrome/messenger/FilterEditor.dtd
mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
mailnews/base/search/content/FilterEditor.js
mailnews/base/search/content/FilterEditor.xul
mailnews/base/search/public/moz.build
mailnews/base/search/public/nsIMsgFilterService.idl
mailnews/base/search/public/nsIMsgOperationListener.idl
mailnews/base/search/public/nsMsgFilterCore.idl
mailnews/base/search/src/nsMsgFilterService.cpp
mailnews/base/util/nsMsgDBFolder.cpp
mailnews/compose/public/nsIMsgSendReport.idl
mailnews/compose/src/nsMsgSend.cpp
mailnews/compose/src/nsMsgSend.h
mailnews/mime/src/mimedrft.cpp
suite/locales/en-US/chrome/mailnews/FilterEditor.dtd
suite/locales/en-US/chrome/mailnews/compose/composeMsgs.properties
--- a/mail/locales/en-US/chrome/messenger/FilterEditor.dtd
+++ b/mail/locales/en-US/chrome/messenger/FilterEditor.dtd
@@ -18,16 +18,18 @@
 
 <!ENTITY contextDesc.label "Apply filter when:">
 <!ENTITY contextIncomingMail.label "Getting New Mail:">
 <!ENTITY contextIncomingMail.accesskey "G">
 <!ENTITY contextManual.label "Manually Run">
 <!ENTITY contextManual.accesskey "R">
 <!ENTITY contextBeforeCls.label "Filter before Junk Classification">
 <!ENTITY contextAfterCls.label "Filter after Junk Classification">
+<!ENTITY contextOutgoing.label "After Sending">
+<!ENTITY contextOutgoing.accesskey "S">
 
 <!ENTITY filterActionDesc.label "Perform these actions:">
 <!ENTITY filterActionDesc.accesskey "P">
 
 <!ENTITY filterActionOrderWarning.label "Note: Filter actions will be run in a different order.">
 <!ENTITY filterActionOrder.label "See execution order">
 
 <!-- New Style Filter Rule Actions -->
--- a/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
@@ -246,16 +246,19 @@ assemblingMailInformation=Assembling mail information…
 gatheringAttachment=Attaching %S…
 creatingMailMessage=Creating mail message…
 
 ## LOCALIZATION NOTE (copyMessageStart): argument %S is folder name
 copyMessageStart=Copying message to %S folder…
 copyMessageComplete=Copy complete.
 copyMessageFailed=Copy failed.
 
+filterMessageComplete=Filter complete.
+filterMessageFailed=Filter failed.
+
 ## LOCALIZATION NOTE (largeMessageSendWarning):
 ## Do not translate %S. It is the size of the message in user-friendly notation.
 largeMessageSendWarning=Warning! You are about to send a message of size %S which may exceed the allowed limit on the mail server. Are you sure that you want to do this?
 
 sendingMessage=Sending message…
 sendMessageErrorTitle=Send Message Error
 postingMessage=Posting message…
 
@@ -353,16 +356,18 @@ smtpEnterPasswordPromptTitle=SMTP Server
 # LOCALIZATION NOTE (removeAttachmentMsgs): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/Localization_and_Plurals
 removeAttachmentMsgs=Remove Attachment;Remove Attachments
 
 ## LOCALIZATION NOTE(errorSavingMsg): Do not translate the word %S. It
 ## will be replaced with the name of the folder the message is being saved to.
 errorSavingMsg=There was an error saving the message to %S. Retry?
 
+errorFilteringMsg=Your message has been sent and saved, but there was an error while running message filters on it.
+
 errorCloudFileAuth.title=Authentication Error
 ## LOCALIZATION NOTE(errorCloudFileAuth.message):
 ## %1$S is the name of the online storage service that authentication failed against.
 errorCloudFileAuth.message=Unable to authenticate to %1$S.
 errorCloudFileUpload.title=Upload Error
 ## LOCALIZATION NOTE(errorCloudFileUpload.message):
 ## %1$S is the name of the online storage service that uploading failed against.
 ## %2$S is the name of the file that failed to upload.
--- a/mailnews/base/search/content/FilterEditor.js
+++ b/mailnews/base/search/content/FilterEditor.js
@@ -56,17 +56,17 @@ function filterEditorOnLoad()
 
     if ("filterList" in args)
     {
       gFilterList = args.filterList;
       // the postPlugin filters cannot be applied to servers that are
       // deferred, (you must define them on the deferredTo server instead).
       let server = gFilterList.folder.server;
       if (server.rootFolder != server.rootMsgFolder)
-        gFilterTypeSelector.disableAfterPlugins();
+        gFilterTypeSelector.disableDeferredAccount();
     }
 
     if ("filterPosition" in args)
     {
       gFilterPosition = args.filterPosition;
     }
 
     if ("filter" in args)
@@ -245,16 +245,18 @@ function initializeFilterTypeSelector()
     checkBoxManual: document.getElementById("runManual"),
     checkBoxIncoming : document.getElementById("runIncoming"),
 
     menulistIncoming: document.getElementById("pluginsRunOrder"),
 
     menuitemBeforePlugins: document.getElementById("runBeforePlugins"),
     menuitemAfterPlugins: document.getElementById("runAfterPlugins"),
 
+    checkBoxOutgoing: document.getElementById("runOutgoing"),
+
     /**
      * Returns the currently set filter type (checkboxes) in terms
      * of a Components.interfaces.nsMsgFilterType value.
      */
     getType: function()
     {
       let type = nsMsgFilterType.None;
 
@@ -269,16 +271,19 @@ function initializeFilterTypeSelector()
           if (getScopeFromFilterList(gFilterList) ==
               nsMsgSearchScope.newsFilter)
             type |= nsMsgFilterType.NewsRule;
           else
             type |= nsMsgFilterType.InboxRule;
         }
       }
 
+      if (this.checkBoxOutgoing.checked)
+        type |= nsMsgFilterType.PostOutgoing;
+
       return type;
     },
 
     /**
      * Sets the checkboxes to represent the filter type passed in.
      *
      * @param aType  the filter type to set in terms
      *               of Components.interfaces.nsMsgFilterType values.
@@ -292,35 +297,38 @@ function initializeFilterTypeSelector()
       this.checkBoxManual.checked   = aType & nsMsgFilterType.Manual;
 
       this.checkBoxIncoming.checked = aType & (nsMsgFilterType.PostPlugin |
                                                nsMsgFilterType.Incoming);
 
       this.menulistIncoming.selectedItem = aType & nsMsgFilterType.PostPlugin ?
         this.menuitemAfterPlugins : this.menuitemBeforePlugins;
 
+      this.checkBoxOutgoing.checked = aType & nsMsgFilterType.PostOutgoing;
+
       this.updateClassificationMenu();
     },
 
     /**
      * Enable the "before/after classification" menulist depending on
      * whether "run when incoming mail" is selected.
      */
     updateClassificationMenu: function()
     {
       this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
       updateFilterType();
     },
 
     /**
-     * Disable the "After classification" option for this filter.
+     * Disable the options unsuitable for deferred accounts.
      */
-    disableAfterPlugins: function()
+    disableDeferredAccount: function()
     {
       this.menuitemAfterPlugins.disabled = true;
+      this.checkboxOutgoing.disabled = true;
     }
   };
 }
 
 function initializeDialog(filter)
 {
   gFilterNameElement.value = filter.filterName;
   let filterType = filter.filterType;
--- a/mailnews/base/search/content/FilterEditor.xul
+++ b/mailnews/base/search/content/FilterEditor.xul
@@ -74,16 +74,22 @@
               <menupopup>
                 <menuitem id="runBeforePlugins"
                           label="&contextBeforeCls.label;"/>
                 <menuitem id="runAfterPlugins"
                           label="&contextAfterCls.label;"/>
               </menupopup>
             </menulist>
           </row>
+          <row>
+            <checkbox id="runOutgoing"
+                      label="&contextOutgoing.label;"
+                      accesskey="&contextOutgoing.accesskey;"
+                      command="cmd_updateFilterType"/>
+          </row>
         </rows>
       </grid>
     </groupbox>
 
     <vbox id="searchTermListBox" flex="1"/>
   </vbox>
 
   <splitter id="gray_horizontal_splitter" persist="state"/>
--- a/mailnews/base/search/public/moz.build
+++ b/mailnews/base/search/public/moz.build
@@ -5,16 +5,17 @@
 
 XPIDL_SOURCES += [
     'nsIMsgFilter.idl',
     'nsIMsgFilterCustomAction.idl',
     'nsIMsgFilterHitNotify.idl',
     'nsIMsgFilterList.idl',
     'nsIMsgFilterPlugin.idl',
     'nsIMsgFilterService.idl',
+    'nsIMsgOperationListener.idl',
     'nsIMsgSearchAdapter.idl',
     'nsIMsgSearchCustomTerm.idl',
     'nsIMsgSearchNotify.idl',
     'nsIMsgSearchScopeTerm.idl',
     'nsIMsgSearchSession.idl',
     'nsIMsgSearchTerm.idl',
     'nsIMsgSearchValidityManager.idl',
     'nsIMsgSearchValidityTable.idl',
--- a/mailnews/base/search/public/nsIMsgFilterService.idl
+++ b/mailnews/base/search/public/nsIMsgFilterService.idl
@@ -9,43 +9,47 @@
 interface nsIMsgFilterList;
 interface nsIMsgWindow;
 interface nsIMsgFilterCustomAction;
 interface nsISimpleEnumerator;
 interface nsIFile;
 interface nsIMsgFolder;
 interface nsIMsgSearchCustomTerm;
 interface nsIArray;
+interface nsIMsgOperationListener;
 
 [scriptable, uuid(f2cd5e30-ac60-46c0-ad29-54321efd4128)]
 interface nsIMsgFilterService : nsISupports {
 
     nsIMsgFilterList OpenFilterList(in nsIFile filterFile, in nsIMsgFolder rootFolder, in nsIMsgWindow msgWindow);
     void CloseFilterList(in nsIMsgFilterList filterList);
 
     void SaveFilterList(in nsIMsgFilterList filterList,
                         in nsIFile filterFile);
 
     void CancelFilterList(in nsIMsgFilterList filterList);
     nsIMsgFilterList getTempFilterList(in nsIMsgFolder aFolder);
     void applyFiltersToFolders(in nsIMsgFilterList aFilterList,
                                in nsIArray aFolders,
-                               in nsIMsgWindow aMsgWindow);
+                               in nsIMsgWindow aMsgWindow,
+                               [optional] in nsIMsgOperationListener aCallback);
 
     /*
      * Apply filters to a specific list of messages in a folder.
      * @param  aFilterType  The type of filter to match against
      * @param  aMsgHdrList  The list of message headers (nsIMsgDBHdr objects)
      * @param  aFolder      The folder the messages belong to
      * @param  aMsgWindow   A UI window for attaching progress/dialogs
+     * @param  aCallback    A listener that gets notified of any filtering error
      */
     void applyFilters(in nsMsgFilterTypeType aFilterType,
                       in nsIArray aMsgHdrList,
                       in nsIMsgFolder aFolder,
-                      in nsIMsgWindow aMsgWindow);
+                      in nsIMsgWindow aMsgWindow,
+                      [optional] in nsIMsgOperationListener aCallback);
 
     /**
      * add a custom filter action
      *
      * @param  aAction   the custom action to add
      */
     void addCustomAction(in nsIMsgFilterCustomAction aAction);
 
new file mode 100644
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgOperationListener.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+// Listener used to notify when an operation has completed.
+[scriptable, uuid(bdaef6ff-0909-435b-8fcd-76525dd2364c)]
+interface nsIMsgOperationListener : nsISupports {
+  /**
+   * Called when the operation stops (possibly with errors)
+   *
+   * @param aStatus Success or failure of the operation
+   */
+  void onStopOperation(in nsresult aStatus);
+};
--- a/mailnews/base/search/public/nsMsgFilterCore.idl
+++ b/mailnews/base/search/public/nsMsgFilterCore.idl
@@ -15,16 +15,17 @@ interface nsMsgFilterType {
   const long InboxJavaScript  = 0x02;
   const long Inbox            = InboxRule | InboxJavaScript;
   const long NewsRule         = 0x04;
   const long NewsJavaScript   = 0x08;
   const long News             = NewsRule | NewsJavaScript;
   const long Incoming         = Inbox | News;
   const long Manual           = 0x10;
   const long PostPlugin       = 0x20; // After bayes filtering
+  const long PostOutgoing     = 0x40; // After sending
   const long All              = Incoming | Manual;
 };
 
 typedef long nsMsgFilterMotionValue;
 
 typedef long nsMsgFilterIndex;
 
 typedef long nsMsgRuleActionType;
--- a/mailnews/base/search/src/nsMsgFilterService.cpp
+++ b/mailnews/base/search/src/nsMsgFilterService.cpp
@@ -38,16 +38,17 @@
 #include "nsArrayEnumerator.h"
 #include "nsMsgMessageFlags.h"
 #include "nsIMsgWindow.h"
 #include "nsIMsgSearchCustomTerm.h"
 #include "nsIMsgSearchTerm.h"
 #include "nsIMsgThread.h"
 #include "nsAutoPtr.h"
 #include "nsIMsgFilter.h"
+#include "nsIMsgOperationListener.h"
 
 #define BREAK_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) {NS_WARNING(_text); break;}
 #define CONTINUE_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) {NS_WARNING(_text); continue;}
 #define BREAK_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) {NS_WARNING(_text); break;}
 #define CONTINUE_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) {NS_WARNING(_text); continue;}
 
 NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService)
 
@@ -252,17 +253,19 @@ nsMsgFilterService::ThrowAlertMsg(const 
 // We are a search listener so that we can build up the list of search hits.
 // Then, when the search is done, we will apply the filter action(s) en-masse, so, for example, if the action is a move,
 // we calls one method to move all the messages to the destination folder. Or, mark all the messages read.
 // In the case of imap operations, or imap/local  moves, the action will be asynchronous, so we'll need to be a url listener
 // as well, and kick off the next filter when the action completes.
 class nsMsgFilterAfterTheFact : public nsIUrlListener, public nsIMsgSearchNotify, public nsIMsgCopyServiceListener
 {
 public:
-  nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow, nsIMsgFilterList *aFilterList, nsIArray *aFolderList);
+  nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
+                          nsIMsgFilterList *aFilterList, nsIArray *aFolderList,
+                          nsIMsgOperationListener *aCallback);
   NS_DECL_ISUPPORTS
   NS_DECL_NSIURLLISTENER
   NS_DECL_NSIMSGSEARCHNOTIFY
   NS_DECL_NSIMSGCOPYSERVICELISTENER
 
   nsresult  AdvanceToNextFolder();  // kicks off the process
 protected:
   virtual ~nsMsgFilterAfterTheFact();
@@ -283,48 +286,56 @@ protected:
   uint32_t                    m_curFilterIndex;
   uint32_t                    m_curFolderIndex;
   uint32_t                    m_numFilters;
   uint32_t                    m_numFolders;
   nsTArray<nsMsgKey>          m_searchHits;
   nsCOMPtr<nsIMutableArray>   m_searchHitHdrs;
   nsTArray<nsMsgKey>          m_stopFiltering;
   nsCOMPtr<nsIMsgSearchSession> m_searchSession;
+  nsCOMPtr<nsIMsgOperationListener> m_callback;
   uint32_t                    m_nextAction; // next filter action to perform
 };
 
 NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify, nsIMsgCopyServiceListener)
 
-nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow, nsIMsgFilterList *aFilterList, nsIArray *aFolderList)
+nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact(nsIMsgWindow *aMsgWindow,
+                                                 nsIMsgFilterList *aFilterList,
+                                                 nsIArray *aFolderList,
+                                                 nsIMsgOperationListener *aCallback)
 {
   m_curFilterIndex = m_curFolderIndex = m_nextAction = 0;
   m_msgWindow = aMsgWindow;
   m_filters = aFilterList;
   m_folders = aFolderList;
   m_filters->GetFilterCount(&m_numFilters);
   m_folders->GetLength(&m_numFolders);
 
   NS_ADDREF(this); // we own ourselves, and will release ourselves when execution is done.
 
   m_searchHitHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  m_callback = aCallback;
 }
 
 nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact()
 {
 }
 
 // do what we have to do to cleanup.
 nsresult nsMsgFilterAfterTheFact::OnEndExecution(nsresult executionStatus)
 {
   if (m_searchSession)
     m_searchSession->UnregisterListener(this);
 
   if (m_filters)
     (void)m_filters->FlushLogIfNecessary();
 
+  if (m_callback)
+    (void)m_callback->OnStopOperation(executionStatus);
+
   Release(); // release ourselves.
   return executionStatus;
 }
 
 nsresult nsMsgFilterAfterTheFact::RunNextFilter()
 {
   nsresult rv = NS_OK;
   while (true)
@@ -838,22 +849,26 @@ NS_IMETHODIMP nsMsgFilterService::GetTem
   nsMsgFilterList *filterList = new nsMsgFilterList;
   NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
   NS_ADDREF(*aFilterList = filterList);
   (*aFilterList)->SetFolder(aFolder);
   filterList->m_temporaryList = true;
   return NS_OK;
 }
 
-NS_IMETHODIMP nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList, nsIArray *aFolders, nsIMsgWindow *aMsgWindow)
+NS_IMETHODIMP
+nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList,
+                                          nsIArray *aFolders,
+                                          nsIMsgWindow *aMsgWindow,
+                                          nsIMsgOperationListener *aCallback)
 {
   NS_ENSURE_ARG_POINTER(aFilterList);
   NS_ENSURE_ARG_POINTER(aFolders);
 
-  nsMsgFilterAfterTheFact *filterExecutor = new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders);
+  nsMsgFilterAfterTheFact *filterExecutor = new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback);
   if (filterExecutor)
     return filterExecutor->AdvanceToNextFolder();
   else
     return NS_ERROR_OUT_OF_MEMORY;
 }
 
 NS_IMETHODIMP nsMsgFilterService::AddCustomAction(nsIMsgFilterCustomAction *aAction)
 {
@@ -922,27 +937,36 @@ nsMsgFilterService::GetCustomTerm(const 
   return NS_OK;
 }
 
 // nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to
 // apply filters to a list of messages, rather than an entire folder
 class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact
 {
 public:
-  nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow, nsIMsgFilterList *aFilterList, nsIArray *aFolderList, nsIArray *aMsgHdrList, nsMsgFilterTypeType aFilterType);
+  nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
+                              nsIMsgFilterList *aFilterList,
+                              nsIArray *aFolderList, nsIArray *aMsgHdrList,
+                              nsMsgFilterTypeType aFilterType,
+                              nsIMsgOperationListener *aCallback);
 
 protected:
   virtual   nsresult  RunNextFilter();
 
   nsCOMArray<nsIMsgDBHdr> m_msgHdrList;
   nsMsgFilterTypeType     m_filterType;
 };
 
-nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow, nsIMsgFilterList *aFilterList, nsIArray *aFolderList, nsIArray *aMsgHdrList, nsMsgFilterTypeType aFilterType)
-: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList),
+nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
+                                                         nsIMsgFilterList *aFilterList,
+                                                         nsIArray *aFolderList,
+                                                         nsIArray *aMsgHdrList,
+                                                         nsMsgFilterTypeType aFilterType,
+                                                         nsIMsgOperationListener *aCallback)
+: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback),
   m_filterType(aFilterType)
 {
   nsCOMPtr<nsISimpleEnumerator> msgEnumerator;
   if (NS_SUCCEEDED(aMsgHdrList->Enumerate(getter_AddRefs(msgEnumerator))))
   {
     uint32_t length;
     if (NS_SUCCEEDED(aMsgHdrList->GetLength(&length)))
       m_msgHdrList.SetCapacity(length);
@@ -1015,32 +1039,34 @@ nsresult nsMsgApplyFiltersToMessages::Ru
   m_curFilter = nullptr;
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to run filters");
   return AdvanceToNextFolder();
 }
 
 NS_IMETHODIMP nsMsgFilterService::ApplyFilters(nsMsgFilterTypeType aFilterType,
                                                nsIArray *aMsgHdrList,
                                                nsIMsgFolder *aFolder,
-                                               nsIMsgWindow *aMsgWindow)
+                                               nsIMsgWindow *aMsgWindow,
+                                               nsIMsgOperationListener *aCallback)
 {
   NS_ENSURE_ARG_POINTER(aFolder);
 
   nsCOMPtr<nsIMsgFilterList>    filterList;
   nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIMutableArray> folderList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   folderList->AppendElement(aFolder, false);
 
   // Create our nsMsgApplyFiltersToMessages object which will be called when ApplyFiltersToHdr
   // finds one or more filters that hit.
-  nsMsgApplyFiltersToMessages * filterExecutor = new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, folderList, aMsgHdrList, aFilterType);
+  nsMsgApplyFiltersToMessages * filterExecutor = new nsMsgApplyFiltersToMessages
+      (aMsgWindow, filterList, folderList, aMsgHdrList, aFilterType, aCallback);
 
   if (filterExecutor)
     return filterExecutor->AdvanceToNextFolder();
 
   return NS_ERROR_OUT_OF_MEMORY;
 }
 
 /* void OnStartCopy (); */
--- a/mailnews/base/util/nsMsgDBFolder.cpp
+++ b/mailnews/base/util/nsMsgDBFolder.cpp
@@ -2344,17 +2344,17 @@ nsMsgDBFolder::OnMessageClassified(const
       nsCOMPtr<nsIMsgFilterService>
         filterService(do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv));
       if (NS_SUCCEEDED(rv))
         // We use a null nsIMsgWindow because we don't want some sort of ui
         // appearing in the middle of automatic filtering (plus I really don't
         // want to propagate that value.)
         rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin,
                                          mPostBayesMessagesToFilter,
-                                         this, nullptr /* nsIMsgWindow */);
+                                         this, nullptr, nullptr);
       mPostBayesMessagesToFilter->Clear();
     }
 
     // Bail if we didn't actually classify any messages.
     if (mClassifiedMsgKeys.IsEmpty())
       return rv;
 
     // Notify that we classified some messages.
--- a/mailnews/compose/public/nsIMsgSendReport.idl
+++ b/mailnews/compose/public/nsIMsgSendReport.idl
@@ -2,17 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIPrompt.idl"
 
 
-[scriptable, uuid(2247c280-7f7f-11d5-9daa-994d49414e7c)]
+[scriptable, uuid(2ec81175-bc65-44b9-ba87-462bc3f938db)]
 interface nsIMsgProcessReport : nsISupports {
 
   attribute boolean proceeded;
   attribute nsresult error;
   attribute wstring message;
   
   void reset();
 };
@@ -20,17 +20,18 @@ interface nsIMsgProcessReport : nsISuppo
 [scriptable, uuid(2247c281-7f7f-11d5-9daa-994d49414e7c)]
 interface nsIMsgSendReport : nsISupports {
 
   const long process_Current = -1;
   const long process_BuildMessage = 0;
   const long process_NNTP = 1;
   const long process_SMTP = 2;
   const long process_Copy = 3;
-  const long process_FCC = 4;
+  const long process_Filter = 4;
+  const long process_FCC = 5;
   
   attribute long deliveryMode;      /* see nsMsgDeliverMode in nsIMsgSend.idl for valid value */
   attribute long currentProcess;
 
   void reset();
   
   void setProceeded(in long process, in boolean proceeded);
   void setError(in long process, in nsresult error, in boolean overwriteError);
--- a/mailnews/compose/src/nsMsgSend.cpp
+++ b/mailnews/compose/src/nsMsgSend.cpp
@@ -71,16 +71,18 @@
 #include "nsComposeStrings.h"
 #include "nsStringGlue.h"
 #include "nsMsgUtils.h"
 #include "nsIArray.h"
 #include "nsArrayUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/mailnews/MimeEncoder.h"
 #include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgFilterService.h"
 
 using namespace mozilla::mailnews;
 
 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
 
 #define PREF_MAIL_SEND_STRUCT "mail.send_struct"
 #define PREF_MAIL_STRICTLY_MIME "mail.strictly_mime"
 #define PREF_MAIL_MESSAGE_WARNING_SIZE "mailnews.message_warning_size"
@@ -273,16 +275,17 @@ nsMsgComposeAndSend::nsMsgComposeAndSend
   m_attachment_pending_count = 0;
   m_status = NS_OK;
   m_plaintext = nullptr;
   m_related_part = nullptr;
   m_related_body_part = nullptr;
   mOriginalHTMLBody = nullptr;
 
   mNeedToPerformSecondFCC = false;
+  mPerformingSecondFCC = false;
 
   mPreloadedAttachmentCount = 0;
   mRemoteAttachmentCount = 0;
   mCompFieldLocalAttachments = 0;
   mCompFieldRemoteAttachments = 0;
   mMessageWarningSize = 0;
 
   mSendReport = new nsMsgSendReport();
@@ -3994,28 +3997,111 @@ nsMsgComposeAndSend::NotifyListenerOnSto
       }
     }
 
     // We failed, and the user decided not to retry. So we're just going to
     // fail out. However, give Fail a success code so that it doesn't prompt
     // the user a second time as they already know about the failure.
     Fail(NS_OK, nullptr, &aStatus);
   }
+
+  if (NS_SUCCEEDED(aStatus) &&
+      !mPerformingSecondFCC && m_messageKey != nsMsgKey_None &&
+      (m_deliver_mode == nsMsgDeliverNow || m_deliver_mode == nsMsgSendUnsent))
+  {
+    nsresult rv = FilterSentMessage();
+    if (NS_FAILED(rv))
+      OnStopOperation(rv);
+    return rv;
+  }
+
+  return MaybePerformSecondFCC(aStatus);
+}
+
+nsresult
+nsMsgComposeAndSend::FilterSentMessage()
+{
+  if (mSendReport)
+    mSendReport->SetCurrentProcess(nsIMsgSendReport::process_Filter);
+
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsresult rv = GetExistingFolder(m_folderName, getter_AddRefs(folder));
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIMsgDBHdr> msgHdr;
+  rv = folder->GetMessageHeader(m_messageKey, getter_AddRefs(msgHdr));
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIMsgFilterService> filterSvc = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = msgArray->AppendElement(msgHdr, false);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsIMsgWindow> msgWindow;
+  if (mSendProgress)
+    mSendProgress->GetMsgWindow(getter_AddRefs(msgWindow));
+
+  return filterSvc->ApplyFilters(nsMsgFilterType::PostOutgoing, msgArray, folder, msgWindow, this);
+}
+
+NS_IMETHODIMP
+nsMsgComposeAndSend::OnStopOperation(nsresult aStatus)
+{
+  // Set a status message...
+  nsString msg;
+  if (NS_SUCCEEDED(aStatus))
+    mComposeBundle->GetStringFromName(MOZ_UTF16("filterMessageComplete"), getter_Copies(msg));
+  else
+    mComposeBundle->GetStringFromName(MOZ_UTF16("filterMessageFailed"), getter_Copies(msg));
+
+  SetStatusMessage(msg);
+
+  if (NS_FAILED(aStatus))
+  {
+    nsresult rv = mComposeBundle->GetStringFromName(MOZ_UTF16("errorFilteringMsg"), getter_Copies(msg));
+    if (NS_SUCCEEDED(rv))
+    {
+      nsCOMPtr<nsIPrompt> prompt;
+      GetDefaultPrompt(getter_AddRefs(prompt));
+      nsMsgDisplayMessageByString(prompt, msg.get(), nullptr);
+    }
+
+    // We failed, however, give Fail a success code so that it doesn't prompt
+    // the user a second time as they already know about the failure.
+    Fail(NS_OK, nullptr, &aStatus);
+  }
+
+  return MaybePerformSecondFCC(aStatus);
+}
+
+nsresult
+nsMsgComposeAndSend::MaybePerformSecondFCC(nsresult aStatus)
+{
   // Ok, now to support a second copy operation, we need to figure
   // out which copy request just finished. If the user has requested
   // a second copy operation, then we need to fire that off, but if they
   // just wanted a single copy operation, we can tell everyone we are done
   // and move on with life. Only do the second copy if the first one worked.
   //
   if ( NS_SUCCEEDED(aStatus) && (mNeedToPerformSecondFCC) )
   {
     if (mSendReport)
       mSendReport->SetCurrentProcess(nsIMsgSendReport::process_FCC);
 
     mNeedToPerformSecondFCC = false;
+    mPerformingSecondFCC = true;
 
     const char *fcc2 = mCompFields->GetFcc2();
     if (fcc2 && *fcc2)
     {
       nsresult rv = MimeDoFCC(mTempFile,
                               nsMsgDeliverNow,
                               mCompFields->GetBcc(),
                               fcc2,
--- a/mailnews/compose/src/nsMsgSend.h
+++ b/mailnews/compose/src/nsMsgSend.h
@@ -125,16 +125,18 @@
 #include "nsIMsgHdr.h"
 #include "nsIMsgIdentity.h"
 #include "nsWeakReference.h"
 #include "nsIDOMWindow.h"
 #include "nsIMsgComposeSecure.h"
 #include "nsAutoPtr.h"
 #include "nsISupportsArray.h"
 #include "nsMsgAttachmentData.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgOperationListener.h"
 
 //
 // Some necessary defines...
 //
 #define TEN_K                 10240
 #define MIME_BUFFER_SIZE      4096 // must be greater than 1000
                                    // SMTP (RFC821) limit
 
@@ -157,24 +159,27 @@ class nsIPrompt;
 class nsIInterfaceRequestor;
 
 namespace mozilla {
 namespace mailnews {
 class MimeEncoder;
 }
 }
 
-class nsMsgComposeAndSend : public nsIMsgSend
+class nsMsgComposeAndSend : public nsIMsgSend,
+                            public nsIMsgOperationListener
 {
   typedef mozilla::mailnews::MimeEncoder MimeEncoder;
 public:
   //
   // Define QueryInterface, AddRef and Release for this class
   //
   NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIMSGSEND
+  NS_DECL_NSIMSGOPERATIONLISTENER
 
   nsMsgComposeAndSend();
 
 
   // Delivery and completion callback routines...
   NS_IMETHOD  DeliverMessage();
   NS_IMETHOD  DeliverFileAsMail();
   NS_IMETHOD  DeliverFileAsNews();
@@ -250,17 +255,16 @@ public:
   // Body processing
   nsresult    SnarfAndCopyBody(const nsACString &attachment1_body,
                                const char  *attachment1_type);
 
   int32_t     PreProcessPart(nsMsgAttachmentHandler  *ma,
                              nsMsgSendPart           *toppart); // The very top most container of the message
                                                                 // For part processing
 
-  NS_DECL_NSIMSGSEND
   nsresult    SetStatusMessage(const nsString &aMsgString);     // Status message method
 
   //
   // All vars necessary for this implementation
   //
   nsMsgKey                  m_messageKey;        // jt -- Draft/Template support; newly created key
   nsCOMPtr<nsIMsgIdentity>  mUserIdentity;
   nsCString                 mAccountKey;
@@ -297,16 +301,17 @@ public:
   nsTArray<nsCString> m_partNumbers;
   //
   // These variables are needed for message Copy operations!
   //
   nsCOMPtr<nsIFile>         mCopyFile;
   nsCOMPtr<nsIFile>         mCopyFile2;
   nsRefPtr<nsMsgCopy>       mCopyObj;
   bool                      mNeedToPerformSecondFCC;
+  bool                      mPerformingSecondFCC;
 
   // For MHTML message creation
   nsCOMPtr<nsIEditor>       mEditor;
 
   //
   // The first attachment, if any (typed in by the user.)
   //
   char                    *m_attachment1_type;
@@ -363,16 +368,18 @@ public:
 
   nsCOMPtr<nsIMsgComposeSecure> m_crypto_closure;
 
 protected:
   nsCOMPtr<nsIStringBundle> mComposeBundle;
   nsresult GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks);
 private:
   virtual ~nsMsgComposeAndSend();
+  nsresult FilterSentMessage();
+  nsresult MaybePerformSecondFCC(nsresult aStatus);
   // will set m_attachment1_body & m_attachment1_body_length;
   nsresult EnsureLineBreaks(const nsCString &aBody);
 
   // generates a message id for our message, if necessary
   void GenerateMessageId( );
 
   // add default custom headers to the message
   nsresult AddDefaultCustomHeaders();
--- a/mailnews/mime/src/mimedrft.cpp
+++ b/mailnews/mime/src/mimedrft.cpp
@@ -1199,16 +1199,17 @@ mime_parse_stream_complete (nsMIMESessio
             PR_Free(subj);
             subj = newSubj;
           }
         }
       }
     }
     else
     {
+      from = MimeHeaders_get(mdd->headers, HEADER_FROM,     false, false);
       repl = MimeHeaders_get(mdd->headers, HEADER_REPLY_TO, false, false);
       to   = MimeHeaders_get(mdd->headers, HEADER_TO,       false, true);
       cc   = MimeHeaders_get(mdd->headers, HEADER_CC,       false, true);
       bcc   = MimeHeaders_get(mdd->headers, HEADER_BCC,       false, true);
 
       /* These headers should not be RFC-1522-decoded. */
       grps = MimeHeaders_get(mdd->headers, HEADER_NEWSGROUPS,  false, true);
       foll = MimeHeaders_get(mdd->headers, HEADER_FOLLOWUP_TO, false, true);
--- a/suite/locales/en-US/chrome/mailnews/FilterEditor.dtd
+++ b/suite/locales/en-US/chrome/mailnews/FilterEditor.dtd
@@ -20,16 +20,18 @@
 
 <!ENTITY contextDesc.label "Apply filter when:">
 <!ENTITY contextIncomingMail.label "Getting New Mail:">
 <!ENTITY contextIncomingMail.accesskey "G">
 <!ENTITY contextManual.label "Manually Run">
 <!ENTITY contextManual.accesskey "R">
 <!ENTITY contextBeforeCls.label "Filter before Junk Classification">
 <!ENTITY contextAfterCls.label "Filter after Junk Classification">
+<!ENTITY contextOutgoing.label "After Sending">
+<!ENTITY contextOutgoing.accesskey "S">
 
 <!ENTITY filterActionDesc.label "Perform these actions:">
 <!ENTITY filterActionDesc.accesskey "P">
 
 <!ENTITY filterActionOrderWarning.label "Note: Filter actions will be run in a different order.">
 <!ENTITY filterActionOrder.label "See execution order">
 
 <!-- New Style Filter Rule Actions -->
--- a/suite/locales/en-US/chrome/mailnews/compose/composeMsgs.properties
+++ b/suite/locales/en-US/chrome/mailnews/compose/composeMsgs.properties
@@ -245,16 +245,19 @@ assemblingMailInformation=Assembling mail information…
 gatheringAttachment=Attaching %S…
 creatingMailMessage=Creating mail message…
 
 ## LOCALIZATION NOTE (copyMessageStart): argument %S is folder name
 copyMessageStart=Copying message to %S folder…
 copyMessageComplete=Copy complete.
 copyMessageFailed=Copy failed.
 
+filterMessageComplete=Filter complete.
+filterMessageFailed=Filter failed.
+
 ## LOCALIZATION NOTE (largeMessageSendWarning):
 ## %S is the message size in user-friendly notation. Do not translate.
 largeMessageSendWarning=Warning! You are about to send a message of size %S which may exceed the allowed limit on the mail server. Are you sure that you want to do this?
 
 sendingMessage=Sending message…
 sendMessageErrorTitle=Send Message Error
 postingMessage=Posting message…
 
@@ -295,8 +298,10 @@ smtpEnterPasswordPrompt=Enter your passw
 ## words %1$S and %2$S. Place the word %1$S where the host name should appear,
 ## and %2$S where the user name should appear.
 smtpEnterPasswordPromptWithUsername=Enter your password for %2$S on %1$S:
 smtpEnterPasswordPromptTitle=SMTP Server Password Required
 
 ## LOCALIZATION NOTE(errorSavingMsg): Do not translate the word %S. It
 ## will be replaced with the name of the folder the message is being saved to.
 errorSavingMsg=There was an error saving the message to %S. Retry?
+
+errorFilteringMsg=Your message has been sent and saved, but there was an error while running message filters on it.