diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for azure-email
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/azure-email.cabal b/azure-email.cabal
new file mode 100644
--- /dev/null
+++ b/azure-email.cabal
@@ -0,0 +1,46 @@
+cabal-version: 2.2
+name:
+  azure-email
+version:
+  0.1.0.0
+synopsis:
+  send email with microsoft azure
+--description:
+homepage:
+  https://github.com/layer-3-communications/azure-email
+license:
+  BSD-3-Clause
+author:
+  layer-3-communications
+maintainer:
+  dcartwright@layer3com.com
+copyright:
+  2018 (c) Layer 3 Communications, LLC.
+category:
+  Web
+build-type:
+  Simple
+extra-source-files:
+  CHANGELOG.md
+
+library
+  exposed-modules:
+    Azure.Email
+  build-depends:
+      base >=4.7 && <4.13
+    , RSA
+    , aeson
+    , base64-bytestring
+    , bytestring
+    , cryptonite
+    , exceptions
+    , http-client
+    , http-client-tls
+    , lens-aeson
+    , microlens
+    , text
+    , time
+    , uuid
+    , wreq
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Azure/Email.hs b/src/Azure/Email.hs
new file mode 100644
--- /dev/null
+++ b/src/Azure/Email.hs
@@ -0,0 +1,176 @@
+{-# LANGUAGE BangPatterns      #-}
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+{-# OPTIONS_GHC -Wall #-}
+
+module Azure.Email
+  ( sendEmail
+  , sendHtmlEmail
+  , fetchAzureToken
+  , Email(..)
+  , AzureToken(..)
+  , AzureCreds(..)
+  ) where
+
+import Codec.Crypto.RSA.Pure (PrivateKey)
+import Control.Monad.Catch (MonadThrow, Exception, throwM)
+import Data.Aeson ((.=),object)
+import Data.Aeson.Lens (_String,key)
+import Data.Function ((&))
+import Data.Functor (void)
+import Data.Monoid((<>))
+import Data.Text (Text)
+import Data.Time (UTCTime, addUTCTime, getCurrentTime)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
+import Data.UUID (UUID)
+import Lens.Micro ((^?),(.~))
+import Network.HTTP.Client (Manager)
+import Network.Wreq
+import qualified Codec.Crypto.RSA.Pure as RSA
+import qualified Data.Aeson as Aeson
+import qualified Data.ByteString.Base64.Lazy as Base64
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as TextEncoding
+import qualified Data.UUID as UUID
+import qualified Data.UUID.V4 as UUID4
+
+data Email = Email
+  { recipientAddress :: !Text
+  , senderAddress    :: !Text
+  , subject          :: !Text
+  , body             :: !Text
+  }
+
+sendEmailInternal ::
+     IsHtml 
+  -> Manager -- ^ HTTP manager
+  -> AzureToken -- ^ token
+  -> Email
+  -> IO ()
+sendEmailInternal isHtml mngr token (Email recipientEmail senderEmail subject' textBody) =
+  void $ postWith opts
+    (Text.unpack (Text.concat ["https://outlook.office365.com/api/v1.0/users/", senderEmail, "/sendmail"]))
+    body'
+  where
+    opts = defaults
+      & manager .~ Right mngr
+      & header "Authorization" .~ [TextEncoding.encodeUtf8 $ "Bearer " <> getAzureToken token]
+    body' = object
+      [ "Message" .= object
+        [ "Subject" .= subject'
+        , "Body"    .= object
+          [ "Content" .= textBody
+          , "ContentType" .= isHtmlToContentType isHtml
+          ]
+        , "ToRecipients" .=
+          [ object
+            [ "EmailAddress" .= object
+               [ "Address" .= recipientEmail
+               ]
+            ]
+          ]
+        ]
+      , "SaveToSentItems" .= True
+      ]
+
+sendEmail ::
+     Manager
+  -> AzureToken
+  -> Email 
+  -> IO ()
+sendEmail = sendEmailInternal IsNotHtml
+
+sendHtmlEmail ::
+     Manager
+  -> AzureToken
+  -> Email
+  -> IO ()
+sendHtmlEmail = sendEmailInternal IsHtml
+
+
+data IsHtml = IsHtml | IsNotHtml
+
+isHtmlToContentType :: IsHtml -> Text
+isHtmlToContentType = \case { IsHtml -> "HTML"; IsNotHtml -> "Text" }
+
+asInt :: Int -> Int; asInt = id
+asText :: Text -> Text; asText = id
+
+buildAssertion :: PrivateKey -> Text -> UUID -> UUID -> UUID -> UTCTime -> Text
+buildAssertion privateKey base64Fingerprint clientId tenantId requestId now =
+  TextEncoding.decodeUtf8 $ BL.toStrict $ mempty
+    <> encodedHeaderAndPayload
+    <> "."
+    <> Base64.encode ( fromRightErr "buildAssertion: RSA signing failed"
+                       $ RSA.sign privateKey encodedHeaderAndPayload
+                     )
+  where
+    encodedHeaderAndPayload = mempty
+      <> Base64.encode (Aeson.encode theHeader)
+      <> "."
+      <> Base64.encode (Aeson.encode payload)
+    theHeader = object
+      [ "alg" .= asText "RS256"
+      , "x5t" .= base64Fingerprint
+      ]
+    payload = object
+      [ "aud" .= Text.concat ["https://login.microsoftonline.com/", uuidToText tenantId, "/oauth2/token"]
+      , "sub" .= uuidToText clientId
+      , "iss" .= uuidToText clientId
+      , "jti" .= uuidToText requestId
+      , "nbf" .= asInt (round $ utcTimeToPOSIXSeconds now)
+      , "exp" .= asInt (round $ utcTimeToPOSIXSeconds $ addUTCTime 180 now)
+      ]
+  
+uuidToText :: UUID -> Text
+uuidToText = TextEncoding.decodeUtf8 . UUID.toASCIIBytes
+
+fetchAzureToken :: Manager -> AzureCreds -> IO AzureToken
+fetchAzureToken mngr creds@(AzureCreds clientId tenantId fingerPrint privateKey) = do
+  let opts = defaults & manager .~ Right mngr
+  now <- getCurrentTime
+  requestId <- UUID4.nextRandom
+  r <- postWith opts (Text.unpack (Text.concat ["https://login.microsoftonline.com/", uuidToText (azureTenantId creds), "/oatuh2/token"]))
+         $ buildFormParams "https://outlook.office365.com/"
+             privateKey
+             fingerPrint
+             clientId
+             tenantId
+             requestId
+             now
+  t <- throwFromJust "Could not get an access token" $ r ^? responseBody . key "access_token" . _String
+  pure $ AzureToken t
+
+buildFormParams :: Text -> PrivateKey -> Text -> UUID -> UUID -> UUID -> UTCTime -> [FormParam]
+buildFormParams resource privateKey base64Fingerprint clientId tenantId requestId now =
+  [ "grant_type"            := asText "client_credentials"
+  , "resource"              := resource
+  , "client_id"             := uuidToText clientId
+  , "client_assertion_type" := asText "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
+  , "client_assertion"      := buildAssertion privateKey base64Fingerprint clientId tenantId requestId now
+  ]
+
+newtype AzureToken = AzureToken { getAzureToken :: Text }
+
+data AzureCreds = AzureCreds
+  { azureClientId    :: !UUID
+  , azureTenantId    :: !UUID
+  , azureFingerprint :: !Text
+  , azurePrivateKey  :: !PrivateKey
+  }
+
+fromRightErr :: String -> Either a b -> b
+fromRightErr err = \case
+  Left  _ -> error err
+  Right b -> b
+
+throwFromJust :: MonadThrow m => String -> Maybe a -> m a
+throwFromJust err = \case
+  Just a  -> pure a
+  Nothing -> throwM (MyException err)
+
+data MyException = MyException String
+  deriving (Show, Eq)
+instance Exception MyException
