diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,21 @@
 
 ## unknown (unknown)
 
+## 0.15 (September 30, 2018)
+
+  * Reimplement `sendMessage` to deal properly with windowset changes made
+    during handling.
+
+  * Add new library functions `windowBracket` and `modifyWindowSet` to
+    `XMonad.Operations`.
+
+## 0.14.2 (August 21, 2018)
+
+### Bug Fixes
+
+  * Add the sample configuration file xmonad.hs again to the release tarball.
+    [https://github.com/xmonad/xmonad/issues/181]
+
 ## 0.14.1 (August 20, 2018)
 
 ### Breaking Changes
diff --git a/man/xmonad.1 b/man/xmonad.1
--- a/man/xmonad.1
+++ b/man/xmonad.1
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.2.1
 .\"
-.TH "XMONAD" "1" "20 August 2018" "Tiling Window Manager" ""
+.TH "XMONAD" "1" "30 September 2018" "Tiling Window Manager" ""
 .hy
 .SH Name
 .PP
diff --git a/man/xmonad.1.html b/man/xmonad.1.html
--- a/man/xmonad.1.html
+++ b/man/xmonad.1.html
@@ -5,7 +5,7 @@
   <meta name="generator" content="pandoc" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
   <meta name="author" content="" />
-  <meta name="dcterms.date" content="2018-08-20" />
+  <meta name="dcterms.date" content="2018-09-30" />
   <title>XMONAD(1) Tiling Window Manager</title>
   <style type="text/css">
       code{white-space: pre-wrap;}
@@ -84,7 +84,7 @@
 <header>
 <h1 class="title">XMONAD(1) Tiling Window Manager</h1>
 <p class="author"></p>
-<p class="date">20 August 2018</p>
+<p class="date">30 September 2018</p>
 </header>
 <nav id="TOC">
 <ul>
diff --git a/man/xmonad.1.markdown b/man/xmonad.1.markdown
--- a/man/xmonad.1.markdown
+++ b/man/xmonad.1.markdown
@@ -1,6 +1,6 @@
 % XMONAD(1) Tiling Window Manager
 %
-% 20 August 2018
+% 30 September 2018
 
 # Name
 
diff --git a/src/XMonad/Operations.hs b/src/XMonad/Operations.hs
--- a/src/XMonad/Operations.hs
+++ b/src/XMonad/Operations.hs
@@ -21,7 +21,7 @@
 import qualified XMonad.StackSet as W
 
 import Data.Maybe
-import Data.Monoid          (Endo(..))
+import Data.Monoid          (Endo(..),Any(..))
 import Data.List            (nub, (\\), find)
 import Data.Bits            ((.|.), (.&.), complement, testBit)
 import Data.Ratio
@@ -30,6 +30,7 @@
 
 import Control.Applicative((<$>), (<*>))
 import Control.Arrow (second)
+import Control.Monad (void)
 import Control.Monad.Reader
 import Control.Monad.State
 import qualified Control.Exception.Extensible as C
@@ -176,6 +177,25 @@
     unless isMouseFocused $ clearEvents enterWindowMask
     asks (logHook . config) >>= userCodeDef ()
 
+-- | Modify the @WindowSet@ in state with no special handling.
+modifyWindowSet :: (WindowSet -> WindowSet) -> X ()
+modifyWindowSet f = modify $ \xst -> xst { windowset = f (windowset xst) }
+
+-- | Perform an @X@ action and check its return value against a predicate p.
+-- If p holds, unwind changes to the @WindowSet@ and replay them using @windows@.
+windowBracket :: (a -> Bool) -> X a -> X a
+windowBracket p action = withWindowSet $ \old -> do
+  a <- action
+  when (p a) . withWindowSet $ \new -> do
+    modifyWindowSet $ \_ -> old
+    windows         $ \_ -> new
+  return a
+
+-- | A version of @windowBracket@ that discards the return value, and handles an
+-- @X@ action reporting its need for refresh via @Any@.
+windowBracket_ :: X Any -> X ()
+windowBracket_ = void . windowBracket getAny
+
 -- | Produce the actual rectangle from a screen and a ratio on that screen.
 scaleRationalRect :: Rectangle -> W.RationalRect -> Rectangle
 scaleRationalRect (Rectangle sx sy sw sh) (W.RationalRect rx ry rw rh)
@@ -371,15 +391,16 @@
 -- Message handling
 
 -- | Throw a message to the current 'LayoutClass' possibly modifying how we
--- layout the windows, then refresh.
+-- layout the windows, in which case changes are handled through a refresh.
 sendMessage :: Message a => a -> X ()
-sendMessage a = do
+sendMessage a = windowBracket_ $ do
     w <- W.workspace . W.current <$> gets windowset
     ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
     whenJust ml' $ \l' ->
-        windows $ \ws -> ws { W.current = (W.current ws)
+        modifyWindowSet $ \ws -> ws { W.current = (W.current ws)
                                 { W.workspace = (W.workspace $ W.current ws)
                                   { W.layout = l' }}}
+    return (Any $ isJust ml')
 
 -- | Send a message to all layouts, without refreshing.
 broadcastMessage :: Message a => a -> X ()
diff --git a/tests/Properties/Delete.hs b/tests/Properties/Delete.hs
--- a/tests/Properties/Delete.hs
+++ b/tests/Properties/Delete.hs
@@ -18,7 +18,7 @@
     where _ = x :: T
 
 -- delete is reversible with 'insert'.
--- It is the identiy, except for the 'master', which is reset on insert and delete.
+-- It is the identity, except for the 'master', which is reset on insert and delete.
 --
 prop_delete_insert (x :: T) =
     case peek x of
diff --git a/xmonad.cabal b/xmonad.cabal
--- a/xmonad.cabal
+++ b/xmonad.cabal
@@ -1,5 +1,5 @@
 name:               xmonad
-version:            0.14.2
+version:            0.15
 synopsis:           A tiling window manager
 description:        xmonad is a tiling window manager for X. Windows are arranged
                     automatically to tile the screen without gaps or overlap, maximising
