diff --git a/mongrel2-handler.cabal b/mongrel2-handler.cabal
--- a/mongrel2-handler.cabal
+++ b/mongrel2-handler.cabal
@@ -1,5 +1,5 @@
 Name:                mongrel2-handler
-Version:             0.1.0
+Version:             0.1.1
 Synopsis:            Mongrel2 Handler Library
 Description:         Mongrel2 Handler Library.
 License:             MIT
@@ -25,6 +25,7 @@
   Extensions:          OverloadedStrings
   Ghc-options:         -Wall -fno-warn-unused-matches
   Hs-source-dirs:      src
-  Exposed-modules:     Mongrel2.Parser
+  Exposed-modules:     Mongrel2
+                       Mongrel2.Parser
                        Mongrel2.Response
                        Mongrel2.Types
diff --git a/src/Mongrel2.hs b/src/Mongrel2.hs
new file mode 100644
--- /dev/null
+++ b/src/Mongrel2.hs
@@ -0,0 +1,72 @@
+module Mongrel2
+       ( Handler
+       , ConnectedHandler
+       , mkHandler
+       , withConnectedHandler
+       , receiveRequest
+       , sendReply
+       -- Re-exported
+       , Message
+       , UUID
+       , ClientID
+       ) where
+
+import Blaze.ByteString.Builder (Builder)
+import Data.Attoparsec
+import Mongrel2.Parser (messageParser, Message, UUID, ClientID)
+import Mongrel2.Response (mkResponse)
+import qualified System.ZMQ as ZMQ
+
+data Handler = Handler { handlerPullFrom :: String
+                       , handlerPublishTo :: String
+                       , handlerId :: Maybe String
+                       }
+
+data ConnectedHandler = ConnectedHandler { chPullSocket :: ZMQ.Socket ZMQ.Pull
+                                         , chPublishSocket :: ZMQ.Socket ZMQ.Pub
+                                         }
+
+-- | Create a new handler. @mkHandler pullFromAddress publishToAddress id@
+-- creates a handler which pulls requests from @fromAddress@ and publishes
+-- replies to @publishAddress@.
+mkHandler :: String -> String -> Maybe String -> Handler
+mkHandler = Handler
+
+-- | Run an IO action with a connected handler.
+withConnectedHandler :: Handler -> (ConnectedHandler -> IO a) -> IO a
+withConnectedHandler h io =
+  ZMQ.withContext 1 $ \c ->
+    ZMQ.withSocket c ZMQ.Pull $ \s -> do
+      ZMQ.withSocket c ZMQ.Pub $ \ps -> do
+        -- Connect to the poll socket.
+        ZMQ.connect s $ handlerPullFrom h
+        -- Bind to publish socket.
+        ZMQ.connect ps $ handlerPublishTo h
+        case handlerId h of
+          Nothing -> return ()
+          Just uuid -> ZMQ.setOption ps $ ZMQ.Identity uuid
+        -- Run the action with the connected handler.
+        io $ ConnectedHandler s ps
+
+-- | Receive a parsed request from the Mongrel2 server. Blocks until
+-- a message is received.
+receiveRequest :: ConnectedHandler -> IO Message
+receiveRequest handler = do
+      msg <- ZMQ.receive (chPullSocket handler) []
+      -- Parse into a structured message.
+      case parseOnly messageParser msg of
+        Left errMsg ->
+          -- Invalid message. This can only happen if there's
+          -- an error in our parsing code or Mongrel2 itself.
+          fail $ "Couldn't parse message: " ++ (show errMsg)
+        Right m -> do
+          -- Return the parsed message.
+          return m
+
+-- | Send a reply to the Mongrel2 server.
+sendReply :: ConnectedHandler -> UUID -> [ClientID] -> Builder -> IO ()
+sendReply handler uuid clientIds replyBuilder = do
+  -- Build the response.
+  let response = mkResponse uuid clientIds replyBuilder
+  -- Send the response.
+  ZMQ.send (chPublishSocket handler) response []
diff --git a/src/Mongrel2/Parser.hs b/src/Mongrel2/Parser.hs
--- a/src/Mongrel2/Parser.hs
+++ b/src/Mongrel2/Parser.hs
@@ -76,5 +76,5 @@
 extractQuery (Object hdrs) =
   case M.lookup "URI" hdrs of
     Just (String uriText) -> decodePath $ encodeUtf8 uriText
-    _ -> error "Missing/invalid 'URI' in headers received from Mongrel2"
-extractQuery _ = error "Invalid headers received from Mongrel2"
+    _ -> fail "Missing/invalid 'URI' in headers received from Mongrel2"
+extractQuery _ = fail "Invalid headers received from Mongrel2"
