diff --git a/Yesod/Core/Handler.hs b/Yesod/Core/Handler.hs
--- a/Yesod/Core/Handler.hs
+++ b/Yesod/Core/Handler.hs
@@ -94,6 +94,7 @@
     , sendWaiApplication
     , sendRawResponse
     , sendRawResponseNoConduit
+    , notModified
       -- * Different representations
       -- $representations
     , selectRep
@@ -112,6 +113,7 @@
     , neverExpires
     , alreadyExpired
     , expiresAt
+    , setEtag
       -- * Session
     , SessionMap
     , lookupSession
@@ -590,6 +592,13 @@
             yield bs
             src' src
 
+-- | Send a 304 not modified response immediately. This is a short-circuiting
+-- action.
+--
+-- Since 1.4.4
+notModified :: MonadHandler m => m a
+notModified = sendWaiResponse $ W.responseBuilder H.status304 [] mempty
+
 -- | Return a 404 not found page. Also denotes no handler available.
 notFound :: MonadHandler m => m a
 notFound = hcError NotFound
@@ -698,6 +707,18 @@
 -- | Set an Expires header to the given date.
 expiresAt :: MonadHandler m => UTCTime -> m ()
 expiresAt = setHeader "Expires" . formatRFC1123
+
+-- | Check the if-none-match header and, if it matches the given value, return
+-- a 304 not modified response. Otherwise, set the etag header to the given
+-- value.
+--
+-- Since 1.4.4
+setEtag :: MonadHandler m => Text -> m ()
+setEtag etag = do
+    mmatch <- lookupHeader "if-none-match"
+    case mmatch of
+        Just x | encodeUtf8 etag == x -> notModified
+        _ -> addHeader "etag" etag
 
 -- | Set a variable in the user's session.
 --
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,1 +1,3 @@
+__1.4.4__ Add the `notModified` and `setEtag` functions.
+
 __1.4.3__ Switch to mwc-random for token generation.
diff --git a/yesod-core.cabal b/yesod-core.cabal
--- a/yesod-core.cabal
+++ b/yesod-core.cabal
@@ -1,5 +1,5 @@
 name:            yesod-core
-version:         1.4.3.1
+version:         1.4.4
 license:         MIT
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
