diff --git a/postmark.cabal b/postmark.cabal
--- a/postmark.cabal
+++ b/postmark.cabal
@@ -1,5 +1,5 @@
 Name:               postmark
-Version:            0.1.1
+Version:            0.2.7
 License:            BSD3
 License-File:       LICENSE
 Author:             Mark Hibberd <mark@hibberd.id.au>
@@ -11,31 +11,29 @@
 Bug-reports:        https://github.com/apiengine/postmark/issues
 Cabal-Version:      >= 1.8
 Build-Type:         Simple
+Tested-With:        GHC == 7.10.3, GHC == 8.0.2
 Description:
-  postmark
+  Library for postmarkapp.com HTTP Api.
 
 Source-Repository   head
   Type:             git
   Location:         https://github.com/apiengine/postmark.git
 
-Flag                small_base
-  Description:      Choose the new, split-up base package.
-
 Flag demo
     Description:   Build the demo executable.
     Default:       False
 
 Library
   Build-Depends:
-                    base                            >= 3          && < 5
-                    , aeson                         >= 0.6
-                    , attoparsec                    >= 0.10
-                    , bytestring                    >= 0.9
-                    , containers                    >= 0.4
-                    , http-types                    >= 0.6
-                    , text                          >= 0.11
-                    , network-api-support           >= 0.1.0
-                    , http-client-tls               >= 0.2.1.1
+                    base                            >= 3 && < 5
+                    , aeson                         >= 0.6 && < 2
+                    , attoparsec                    >= 0.10 && < 0.14
+                    , bytestring                    >= 0.9 && < 0.11
+                    , containers                    >= 0.4 && < 0.7
+                    , http-types                    >= 0.6 && < 1
+                    , text                          >= 0.11 && < 1.3
+                    , network-api-support           >= 0.3.0 && < 0.4
+                    , http-client-tls               >= 0.2.1.1 && < 0.4
 
 
   GHC-Options:
@@ -54,15 +52,16 @@
                     Network.Api.Postmark.Settings
                     Network.Api.Postmark.Tutorial
 
+
 executable         postmark-demo
     if flag(demo)
         Buildable: True
     else
         Buildable: False
 
-    ghc-options:   -Wall -threaded -O2
+    ghc-options:   -Wall -threaded
     main-is:           ../demo/demo.hs
-    hs-source-dirs:    dist demo
+    hs-source-dirs:    demo
     build-depends:     base
                      , postmark
                      , text
diff --git a/src/Network/Api/Postmark.hs b/src/Network/Api/Postmark.hs
--- a/src/Network/Api/Postmark.hs
+++ b/src/Network/Api/Postmark.hs
@@ -7,11 +7,12 @@
 --
 -- Library for postmarkapp.com HTTP Api.
 --
--- To get start see some examples in the 'Network.Api.Postmark.Tutorial' module at <http://hackage.haskell.org/packages/archive/postmark/0.0.2/doc/html/Network-Api-Postmark-Tutorial.html>.
+-- To get start see some examples in the "Network.Api.Postmark.Tutorial" module.
 --
 -- Source and more information can be found at <https://github.com/apiengine/postmark>.
 --
 -- To experiment with a live demo try:
+--
 -- > $ git clone https://github.com/apiengine/postmark
 -- > $ cd postmark
 -- > $ cabal install --only-dependencies &&  cabal configure -f demo  && cabal build
@@ -19,11 +20,39 @@
 --
 -- Issues can be reported at <https://github.com/apiengine/postmark/issues>.
 --
-module Network.Api.Postmark (module X) where
+module Network.Api.Postmark (
 
-import Network.Api.Postmark.Core as X
-import Network.Api.Postmark.Data as X hiding (ojson, oljson, omjson, toText)
-import Network.Api.Postmark.Error as X
-import Network.Api.Postmark.Request as X
-import Network.Api.Postmark.Response as X
-import Network.Api.Postmark.Settings as X
+  -- * Settings
+  PostmarkSettings (..), PostmarkApiToken, postmarkHttp, postmarkHttps,
+  -- ** Using the test token
+  postmarkTestToken, postmarkHttpTest, postmarkHttpsTest,
+
+  -- * Sending email
+  email, emails, Email (..), defaultEmail,
+  -- ** Using a template
+  emailWithTemplate, EmailWithTemplate (..), defaultEmailWithTemplate,
+  -- ** Tracking links
+  TrackLinks (..),
+  -- ** Attachments
+  Attachment (..),
+  -- ** Response type
+  Sent (..),
+
+  -- * Error types
+  PostmarkError (..), PostmarkErrorType (..),
+
+  -- * Lower-level API
+  -- ** Request
+  request, PostmarkRequest (..), PostmarkRequest',
+  -- ** Response
+  PostmarkResponse (..), PostmarkUnexpectedType (..),
+  PostmarkResponse', syntaxErr, formatErr,
+
+) where
+
+import Network.Api.Postmark.Core
+import Network.Api.Postmark.Data
+import Network.Api.Postmark.Error
+import Network.Api.Postmark.Request
+import Network.Api.Postmark.Response
+import Network.Api.Postmark.Settings
diff --git a/src/Network/Api/Postmark/Core.hs b/src/Network/Api/Postmark/Core.hs
--- a/src/Network/Api/Postmark/Core.hs
+++ b/src/Network/Api/Postmark/Core.hs
@@ -1,8 +1,14 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Network.Api.Postmark.Core (
+
+  -- * API endpoints
   email,
   emails,
+  emailWithTemplate,
+
+  -- * Run a request
   request
+
 ) where
 
 import qualified Data.ByteString.Lazy as BL
@@ -28,6 +34,9 @@
 emails :: [Email] -> PostmarkRequest' [Sent]
 emails es = PostmarkRequest POST "/email/batch" $ setJson es
 
+-- | Send a single email using a template: https://postmarkapp.com/developer/api/templates-api#email-with-template
+emailWithTemplate :: EmailWithTemplate -> PostmarkRequest' Sent
+emailWithTemplate ewt = PostmarkRequest POST "/email/withTemplate" $ setJson ewt
 
 -- * Run a request
 
diff --git a/src/Network/Api/Postmark/Data.hs b/src/Network/Api/Postmark/Data.hs
--- a/src/Network/Api/Postmark/Data.hs
+++ b/src/Network/Api/Postmark/Data.hs
@@ -1,8 +1,32 @@
 {-# LANGUAGE OverloadedStrings, GADTSyntax #-}
-module Network.Api.Postmark.Data where
+module Network.Api.Postmark.Data (
 
-import Control.Applicative
+  -- * Request types
 
+  -- ** Email
+  Email (..),
+  defaultEmail,
+
+  -- ** Email with template
+  EmailWithTemplate (..),
+  defaultEmailWithTemplate,
+
+  -- ** Track links
+  TrackLinks (..),
+
+  -- ** Attachment
+  Attachment (..),
+
+  -- ** Response type
+  Sent (..),
+
+  -- * Internal Json tools
+  ojson,
+  oljson,
+  omjson,
+  toText
+) where
+
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text.Lazy.Encoding as LE
@@ -34,15 +58,47 @@
   , emailText :: Maybe Text
   , emailReplyTo :: Text
   , emailHeaders :: Map Text Text
+  , emailTrackOpens :: Maybe Bool
+  , emailTrackLinks :: Maybe TrackLinks
   , emailAttachments :: [Attachment]
   }
 
+-- | When “link tracking” is enabled, Postmark will record statistics when a
+-- user clicks on a link in an email. You can use this feature to determine
+-- if a particular recipient has clicked a link that was emailed to them.
+--
+-- https://postmarkapp.com/developer/user-guide/tracking-links#enabling-link-tracking
+data TrackLinks
+  = None        -- ^ No links will be replaced or tracked.
+  | HtmlAndText -- ^ Links will be replaced in both HTML and text bodies.
+  | HtmlOnly    -- ^ Links will be replaced in only the HTML body. You may
+                --   want this option if you do not want encoded tracking
+                --   links to appear in the plain text of an email.
+  | TextOnly    -- ^ Links will be replaced in only the text body.
+  deriving (Show)
+
 data Attachment = Attachment {
     attachmentName :: Text
   , attachmentContent :: Text
   , attachmentContentType :: Text
   }
 
+data EmailWithTemplate = EmailWithTemplate {
+    templateId :: Int
+  , templateModel :: Map Text Text
+  , inlineCss :: Bool
+  , emailFrom' :: Text
+  , emailTo' :: [Text]
+  , emailCc' :: [Text]
+  , emailBcc' :: [Text]
+  , emailTag' :: Maybe Text
+  , emailReplyTo' :: Text
+  , emailHeaders' :: Map Text Text
+  , emailTrackOpens' :: Maybe Bool
+  , emailTrackLinks' :: Maybe TrackLinks
+  , emailAttachments' :: [Attachment]
+  }
+
 defaultEmail :: Email
 defaultEmail = Email {
     emailFrom = ""
@@ -55,9 +111,27 @@
   , emailText = Nothing
   , emailReplyTo = ""
   , emailHeaders = M.empty
+  , emailTrackOpens = Nothing
+  , emailTrackLinks = Nothing
   , emailAttachments = []
   }
 
+defaultEmailWithTemplate :: EmailWithTemplate
+defaultEmailWithTemplate = EmailWithTemplate {
+    templateId = 0
+  , templateModel = M.empty
+  , inlineCss = False
+  , emailFrom' = ""
+  , emailTo' = []
+  , emailCc' = []
+  , emailBcc' = []
+  , emailTag' = Nothing
+  , emailReplyTo' = ""
+  , emailHeaders' = M.empty
+  , emailTrackOpens' = Nothing
+  , emailTrackLinks' = Nothing
+  , emailAttachments' = []
+  }
 
 instance ToJSON Email where
   toJSON v = object ([
@@ -72,15 +146,50 @@
     , oljson "Cc" (emailCc v) (T.intercalate ",")
     , oljson "Bcc" (emailBcc v) (T.intercalate ",")
     , omjson "Headers" (emailHeaders v)
+    , ojson "TrackOpens" (emailTrackOpens v)
+    , ojson "TrackLinks" (emailTrackLinks v)
     , oljson "Attachments" (emailAttachments v) id
     ])
 
+{- The reason we are being explicit here is because the serialized constructors
+   for TrackLinks match the possible values in the Postmark API.
+   We don't want to send values wholesale if new constructors come along.
+-}
+instance ToJSON TrackLinks where
+  toJSON v =
+    toJSON $ case v of
+      None ->
+        "None" :: Text
+      HtmlAndText ->
+        "HtmlAndText"
+      HtmlOnly ->
+        "HtmlOnly"
+      TextOnly ->
+        "TextOnly"
+
 instance ToJSON Attachment where
   toJSON v = object [
       "Name" .= attachmentName v
     , "Content" .= attachmentContent v
     , "ContentType" .= attachmentContentType v
     ]
+
+instance ToJSON EmailWithTemplate where
+  toJSON v = object ([
+      "TemplateId" .= templateId v
+    , "TemplateModel" .= templateModel v
+    , "From" .= (emailFrom' v)
+    , "To" .= T.intercalate "," (emailTo' v)
+    , "ReplyTo" .= emailReplyTo' v
+    ] ++ catMaybes [
+      ojson "Tag" (emailTag' v)
+    , oljson "Cc" (emailCc' v) (T.intercalate ",")
+    , oljson "Bcc" (emailBcc' v) (T.intercalate ",")
+    , omjson "Headers" (emailHeaders' v)
+    , ojson "TrackOpens" (emailTrackOpens' v)
+    , ojson "TrackLinks" (emailTrackLinks' v)
+    , oljson "Attachments" (emailAttachments' v) id
+    ])
 
 -- * Response types
 
diff --git a/src/Network/Api/Postmark/Error.hs b/src/Network/Api/Postmark/Error.hs
--- a/src/Network/Api/Postmark/Error.hs
+++ b/src/Network/Api/Postmark/Error.hs
@@ -1,7 +1,8 @@
 {-# LANGUAGE OverloadedStrings, GADTSyntax #-}
-module Network.Api.Postmark.Error where
-
-import Control.Applicative
+module Network.Api.Postmark.Error (
+  PostmarkError (..),
+  PostmarkErrorType (..)
+) where
 
 import Data.Aeson
 import Data.Text
@@ -27,6 +28,13 @@
   | PostmarkBounceQueryException
   | PostmarkJsonRequired
   | PostmarkTooManyMessages
+  | PostmarkTemplateQueryException
+  | PostmarkTemplateNotFound
+  | PostmarkTemplateLimitWouldBeExceeded
+  | PostmarkTemplateNoDataReceived
+  | PostmarkTemplateRequiredFieldMissing
+  | PostmarkTemplateFieldTooLarge
+  | PostmarkTemplateFieldInvalid
   | PostmarkUnkownError Int
   deriving Eq
 
@@ -46,6 +54,13 @@
           408 -> PostmarkBounceQueryException
           409 -> PostmarkJsonRequired
           410 -> PostmarkTooManyMessages
+          1100 -> PostmarkTemplateQueryException
+          1101 -> PostmarkTemplateNotFound
+          1105 -> PostmarkTemplateLimitWouldBeExceeded
+          1109 -> PostmarkTemplateNoDataReceived
+          1120 -> PostmarkTemplateRequiredFieldMissing
+          1121 -> PostmarkTemplateFieldTooLarge
+          1122 -> PostmarkTemplateFieldInvalid
           _ -> PostmarkUnkownError code) (o .: "ErrorCode"))
     <*> (o .: "Message")
   parseJSON _ = fail "Invalid Postmark Error Response"
@@ -75,5 +90,19 @@
     "Your HTTP request does not have the Accept and Content-Type headers set to application/json."
   show PostmarkTooManyMessages =
     "Your batched request contains more than 500 messages."
+  show PostmarkTemplateQueryException =
+    "The value of a GET parameter for the request is not valid."
+  show PostmarkTemplateNotFound =
+     "TemplateId not found. The TemplateId references a Template that does not exist, or is not associated with the Server specified for this request."
+  show PostmarkTemplateLimitWouldBeExceeded =
+     "Template limit would be exceeded. A Server may have up to 300 active templates, processing this request would exceed this limit."
+  show PostmarkTemplateNoDataReceived =
+      "No Template data received. You didn’t provide JSON body parameters in your request. Refer to the Template API reference for more details on required parameters."
+  show PostmarkTemplateRequiredFieldMissing =
+      "A required Template field is missing. A required field is missing from the body of the POST request."
+  show PostmarkTemplateFieldTooLarge =
+      "Template field is too large. One of the values of the request's body exceeds our size restrictions for that field."
+  show PostmarkTemplateFieldInvalid =
+      "A Templated field has been submitted that is invalid. One of the fields of the request body is invalid."
   show (PostmarkUnkownError code) =
     "An unexpected error code [" ++ show code ++ "] was retured from postmark."
diff --git a/src/Network/Api/Postmark/Request.hs b/src/Network/Api/Postmark/Request.hs
--- a/src/Network/Api/Postmark/Request.hs
+++ b/src/Network/Api/Postmark/Request.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE GADTs, GADTSyntax #-}
-module Network.Api.Postmark.Request where
+module Network.Api.Postmark.Request (
+  PostmarkRequest (..),
+  PostmarkRequest'
+) where
 
 import Network.Api.Postmark.Error
 
diff --git a/src/Network/Api/Postmark/Response.hs b/src/Network/Api/Postmark/Response.hs
--- a/src/Network/Api/Postmark/Response.hs
+++ b/src/Network/Api/Postmark/Response.hs
@@ -1,4 +1,10 @@
-module Network.Api.Postmark.Response where
+module Network.Api.Postmark.Response (
+  PostmarkResponse (..),
+  PostmarkUnexpectedType (..),
+  PostmarkResponse',
+  syntaxErr,
+  formatErr
+) where
 
 import qualified Data.ByteString.Lazy as BL
 import Data.Text
diff --git a/src/Network/Api/Postmark/Settings.hs b/src/Network/Api/Postmark/Settings.hs
--- a/src/Network/Api/Postmark/Settings.hs
+++ b/src/Network/Api/Postmark/Settings.hs
@@ -1,25 +1,70 @@
 {-# LANGUAGE OverloadedStrings #-}
-module Network.Api.Postmark.Settings where
+module Network.Api.Postmark.Settings (
 
+  -- * Settings types
+  PostmarkSettings (..),
+  PostmarkApiToken,
+
+  -- * Settings construction
+  postmarkHttp,
+  postmarkHttps,
+
+  -- * Using the test token
+  postmarkTestToken,
+  postmarkHttpTest,
+  postmarkHttpsTest
+
+) where
+
 import Data.Text
 
+-- | The Postmark “server token” which is sent via the
+-- @X-Postmark-Server-Token@ HTTP header. You can find your server
+-- token under the “Credentials” tab on the Postmark website.
+--
+-- If you do not yet have a Postmark account, or if you want to send
+-- test emails that don't actually get delivered, you may use
+-- 'postmarkTestToken'.
+--
+-- https://postmarkapp.com/developer/api/overview#authentication
+type PostmarkApiToken = Text
+
+-- | To construct 'PostmarkSettings', use 'postmarkHttps' or
+-- 'postmarkHttp'.
+--
+-- Or to use the test API instead, use 'postmarkHttpsTest' or
+-- 'postmarkHttpTest'.
 data PostmarkSettings =
   PostmarkSettings {
       apiUrl :: Text
-    , apiToken :: Text
+    , apiToken :: PostmarkApiToken
     } deriving (Eq, Show)
 
-postmarkTestToken :: Text
+-- | An API token that you can use when you want to send test emails that
+-- don't actually get delivered to the recipient.
+--
+-- https://postmarkapp.com/developer/api/overview#authentication
+postmarkTestToken :: PostmarkApiToken
 postmarkTestToken = "POSTMARK_API_TEST"
 
+-- | Postmark settings using the HTTP protocol and the test API token
+-- ('postmarkTestToken').
+--
+-- HTTPS is recommended instead ('postmarkHttpsTest').
 postmarkHttpTest :: PostmarkSettings
 postmarkHttpTest = postmarkHttp postmarkTestToken
 
+-- | Postmark settings using the HTTPS protocol and the test API token
+-- ('postmarkTestToken').
 postmarkHttpsTest :: PostmarkSettings
 postmarkHttpsTest = postmarkHttps postmarkTestToken
 
-postmarkHttp :: Text -> PostmarkSettings
+-- | Constructs Postmark settings using the HTTP protocol and your API token.
+--
+-- HTTPS is recommended instead ('postmarkHttps').
+postmarkHttp :: PostmarkApiToken -> PostmarkSettings
 postmarkHttp = PostmarkSettings "http://api.postmarkapp.com"
 
-postmarkHttps :: Text -> PostmarkSettings
+-- | Constructs Postmark settings using the HTTPS protocol and your API token.
+postmarkHttps :: PostmarkApiToken -> PostmarkSettings
 postmarkHttps = PostmarkSettings "https://api.postmarkapp.com"
diff --git a/src/Network/Api/Postmark/Tutorial.hs b/src/Network/Api/Postmark/Tutorial.hs
--- a/src/Network/Api/Postmark/Tutorial.hs
+++ b/src/Network/Api/Postmark/Tutorial.hs
@@ -50,7 +50,7 @@
 
 > import Network.Api.Postmark
 >
-> demo = request postmarkHttpTrest $ emails [
+> demo = request postmarkHttpTest $ emails [
 >     defaultEmail {
 >       emailFrom = "demo-from@postmark.hs"
 >     , emailTo = ["demo-to@postmark.hs"]
