用Mailgun邮寄出去!

上周 ,我们通过将Haskell与Twilio集成,开始了API世界的探索。 我们能够发送基本的SMS消息,然后创建可以响应用户消息的服务器。 本周,我们将尝试另一种效果:发送电子邮件。 我们将使用Mailgun以及此任务的Hailgun Haskell API。

您可以通过查看我们的Github存储库中的mailgun分支来查看本文的完整代码。 如果本文激发了您对更多Haskell库的好奇心,则应下载我们的生产清单

开户

首先,我们显然需要一个mailgun帐户。 注册是免费和直接的。 它会要求您提供一个电子邮件域,但是您不需要一个就可以开始使用。 只要您处于测试模式,就可以使用它们提供的沙箱域来托管邮件服务器。

使用Twilio,我们必须指定一个可以在测试模式下发送消息的“经过验证的”电话号码。 同样,您还需要指定一个经过验证的电子邮件地址。 您的沙盒域只能发送到该地址。 您还需要保存一些有关Mailgun帐户的信息。 特别是,您需要使用API​​密钥,沙盒电子邮件域以及电子邮件的回复地址。 将这些另存为环境变量在本地系统和远程计算机上。

基本电子邮件

现在,通过发送基本电子邮件来了解Hailgun代码。 所有这些都发生在简单的IO monad中。 我们最终想要使用sendEmail函数,该函数同时需要HailgunContextHailgunMessage

sendEmail
:: HailgunContext
-> HailgunMessage
-> IO (Either HailgunErrorResponse HailgunSendResponse)

我们将从检索环境变量开始。 使用域和API密钥,我们可以构建需要作为参数传递的HailgunContext

import Data.ByteString.Char8 (pack)
sendMail :: IO ()
sendMail = do
domain <- getEnv “MAILGUN_DOMAIN”
apiKey <- getEnv “MAILGUN_API_KEY”
replyAddress <- pack <$> getEnv “MAILGUN_REPLY_ADDRESS”
-- Last argument is an optional proxy
let context = HailgunContext domain apiKey Nothing
...

现在要构建消息本身,我们将使用构建器函数hailgunMessage 。 它采用几个不同的参数:

hailgunMessage
:: MessageSubject
-> MessageContent
-> UnverifiedEmailAddress -- Reply Address, just a ByteString
-> MessageRecipients
-> [Attachment]
-> Either HailgunErrorMessage HailgunMessage

这些都很容易填写MessageSubjectText ,然后我们将从上方传递回复地址。 对于内容,我们将从对纯文本电子邮件使用TextOnly构造函数开始。 稍后我们将看到一个示例,说明如何在内容中使用HTML:

sendMail :: IO ()
sendMail = do

replyAddress <- pack <$> getEnv “MAILGUN_REPLY_ADDRESS”
let msg = mkMessage replyAddress

where
mkMessage replyAddress = hailgunMessage
“Hello Mailgun!”
(TextOnly “This is a test message.”)
replyAddress
...

MessageRecipients类型具有三个字段。 首先是直接收件人,然后是抄送的电子邮件,然后是密件抄送的用户。 目前,我们仅发送给单个用户。 因此,我们可以使用emptyMessageRecipients项目并进行修改。 现在,通过提供一个空的附件列表来结束我们的构造:

where
mkMessage replyAddress = hailgunMessage
“Hello Mailgun!”
(TextOnly “This is a test message.”)
replyAddress
(emptyMessageRecipients { recipientsTo = [“verified@mail.com”] } )
[]

如果存在问题, hailgunMessage函数和sendEmail函数本身也会引发错误。 但是,只要我们检查这些错误,我们就可以很好地发送电子邮件!

createAndSendEmail :: IO ()
createAndSendEmail = do
domain <- getEnv “MAILGUN_DOMAIN”
apiKey <- getEnv “MAILGUN_API_KEY”
replyAddress <- pack <$> getEnv “MAILGUN_REPLY_ADDRESS”
let context = HailgunContext domain apiKey Nothing
let msg = mkMessage replyAddress
case msg of
Left err -> putStrLn (“Making failed: “ ++ show err)
Right msg' -> do
result <- sendEmail context msg
case result of
Left err -> putStrLn (“Sending failed: “ ++ show err)
Right resp -> putStrLn (“Sending succeeded: “ ++ show rep)

注意,当我们从类型定义开始时,构建所有函数非常容易。 我们可以研究每种类型并弄清楚它需要什么。 我在有关编译驱动学习的这篇文章中对此思想进行了更多的反思,该文章是我们为Haskell新手准备的Haskell Brain系列的一部分!

验证电子邮件

现在,我们希望将发送电子邮件合并到我们的服务器中。 从源代码中可以看到,我对Servant服务器进行了改进,以使用免费的monad。 我们的系统中有许多不同的效果,这有助于我们使它们保持直线。 请查看本文,以获取有关免费monad和Eff库的更多详细信息。 首先,我们要描述发送电子邮件的效果。 我们将从具有单个构造函数的简单数据类型开始:

data Email a where
SendSubscribeEmail :: Text -> Email (Either String ())
sendSubscribeEmail :: (Member Email r)
=> Text -> Eff r (Either String ())
sendSubscribeEmail email = send (SendSubscribeEmail email)

现在,我们需要一种剥离Email效果的方法,只要有IO可以执行。 我们将模仿已经作为转换编写的sendEmail函数。 现在,我们将发送到的用户电子邮件作为输入!

runEmail :: (Member IO r) => Eff (Email ': r) a -> Eff ra
runEmail = runNat emailToIO
where
emailToIO :: Email a -> IO a
emailToIO (SendSubscribeEmail subEmail) = do
domain <- getEnv "MAILGUN_DOMAIN"
apiKey <- getEnv "MAILGUN_API_KEY"
replyEmail <- pack <$> getEnv "MAILGUN_REPLY_ADDRESS"
let context = HailgunContext domain apiKey Nothing
case mkSubscribeMessage replyEmail (encodeUtf8 subEmail) of
Left err -> return $ Left err
Right msg -> do
result <- sendEmail context msg
case result of
Left err -> return $ Left (show err)
Right resp -> return $ Right ()

扩展我们的短信处理程序

现在,我们已经正确地描述了发送电子邮件的效果,让我们将其合并到服务器中! 我们将从编写另一种数据类型开始,这些数据类型将代表用户可能会发短信给我们的潜在命令。 目前,它仅具有“ subscribe”命令。

data SMSCommand = SubscribeCommand Text

现在,让我们编写一个函数,该函数将接收其消息并将其解释为命令。 如果他们发短信subscribe {email} ,我们将向他们发送电子邮件!

messageToCommand :: Text -> Maybe SMSCommand
messageToCommand messageBody = case splitOn " " messageBody of
["subscribe", email] -> Just $ SubscribeCommand email
_ -> Nothing

现在,我们将扩展服务器处理程序以进行回复。 如果我们正确解释了他们的命令,我们将发送电子邮件! 否则,我们会给他们发回一条短信,说我们听不懂。 请注意,我们的SMS效果和Email效果如何成为此处理程序的一部分:

smsHandler :: (Member SMS r, Member Email r)
=> IncomingMessage -> Eff r ()
smsHandler msg =
case messageToCommand (body msg) of
Nothing -> sendText (fromNumber msg)
"Sorry, we didn't understand that request!"
Just (SubscribeCommand email) -> do
_ <- sendSubscribeEmail email
return ()

现在,当用户“订阅”时,我们的服务器将能够发送电子邮件!

附加文件

让我们将电子邮件变得更加复杂。 目前,我们仅发送非常基本的电子邮件。 让我们对其进行修改,使其具有附件。 我们可以通过提供文件路径以及描述该文件的字符串来构建附件。 要获取此文件,我们的消息传递功能将需要当前的运行目录。 我们还将稍微改变一下身体。

mkSubscribeMessage :: ByteString -> ByteString -> FilePath -> Either HailgunErrorMessage HailgunMessage
mkSubscribeMessage replyAddress subscriberAddress currentDir =
hailgunMessage
"Thanks for signing up!"
content
replyAddress
(emptyMessageRecipients { recipientsTo = [subscriberAddress] })
-- Notice the attachment!
[ Attachment
(rewardFilepath currentDir)
(AttachmentBS "Your Reward")
]
where
content = TextOnly "Here's your reward!”
rewardFilepath :: FilePath -> FilePath
rewardFilepath currentDir = currentDir ++ "/attachments/reward.txt"

现在,当我们的用户注册时,他们将获得我们指定的任何附件文件!

HTML内容

为了展示更多功能,让我们更改电子邮件的内容,使其包含一些HTML而不是仅文本! 特别是,我们将使他们有机会通过单击指向我们服务器的链接来确认其订阅。 此处所有更改的是,我们将使用TextAndHTML构造函数而不是TextOnly 。 我们确实希望提供电子邮件的纯文本解释,以防由于某种原因无法呈现HTML。 注意链接使用<a>标记:

content = TextAndHTML
textOnly
("Here's your reward! To confirm your subscription, click " <>
link <> "!")
where
textOnly = "Here's your reward! To confirm your subscription, go to "
<> "https://haskell-apis.herokuapp.com/api/subscribe/"
<> subscriberAddress
<> " and we'll sign you up!"
link = "<a href=\"https://haskell-apis.herokuapp.com/api/subscribe/"
<> subscriberAddress <> "\">this link</a>"

现在,我们将添加另一个端点,该端点将捕获电子邮件作为参数并将其保存到数据库中。 Database效果与Eff文章中的效果非常相似。 它将电子邮件保存在数据库表中。

type ServerAPI = "api" :> "ping" :> Get '[JSON] String :<|>
"api" :> "sms" :> ReqBody '[FormUrlEncoded] IncomingMessage
:> Post '[JSON] () :<|>
"api" :> "subscribe" :> Capture "email" Text :> Get '[JSON] ()
subscribeHandler :: (Member Database r) => Text -> Eff r ()
subscribeHandler email = registerUser email

现在,如果我们想编写一个可以向系统中的所有人发送电子邮件的函数,那一点也不难! 我们扩展了EmailDatabase效果类型。 Database功能将检索我们系统中的所有订户。 同时, Email效果会将指定的电子邮件发送到整个列表。

data Database a where
RegisterUser :: Text -> Database ()
RetrieveSubscribers :: Database [Text]
data Email a where
SendSubscribeEmail :: Text -> Email (Either String ())
-- First parameter is (Subject line, Text content, HTML Context)
SendEmailToList
:: (Text, ByteString, Maybe ByteString)
-> [Text]
-> Email (Either String ())

合并这些只需要使用两种效果:

sendEmailToList :: (Member Email r, Member Database r) => ByteString -> ByteString -> Eff r ()
sendEmailToList = do
list <- retrieveSubscribers
void $ sendEmailToList list

请注意,没有任何lift呼叫! 这是Eff强大优势之一。

结论

正如我们在本文中所看到的,使用Haskell发送电子邮件并不太可怕。 Hailgun API非常直观,当您逐一分解内容并查看涉及的类型时。 本文汇集了来自编译驱动开发和Eff框架的想法。 特别是,我们可以在本系列文章中看到用Eff分开效果的便捷程度,这样我们就不会做很多麻烦的举动了。

本文中有许多高级材料,因此,如果您认为您需要回溯,请不要担心,我们已为您服务! 我们的Haskell Web技能系列将教您如何使用Persistent之类的库进行数据库管理,以及使用Servant编写API。 对于更多库,您可以用来编写增强的Haskell,请下载我们的生产清单

如果您从未使用过Haskell编程,则应该尝试一下! 下载我们的Haskell初学者清单或阅读我们的提升系列

From: https://hackernoon.com/mailing-it-out-with-mailgun-9302400bed52

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值