From 7c289c243d64259d3b2ec40b0393873913aa7a5b Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Wed, 28 Aug 2019 19:23:11 -0400 Subject: [PATCH] blog: add post on how to send email with nim (#71) * blog: add post on how to send email with nim * Update how-send-email-nim-2019-08-28.markdown * Update how-send-email-nim-2019-08-28.markdown --- blog/how-send-email-nim-2019-08-28.markdown | 135 ++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 blog/how-send-email-nim-2019-08-28.markdown diff --git a/blog/how-send-email-nim-2019-08-28.markdown b/blog/how-send-email-nim-2019-08-28.markdown new file mode 100644 index 0000000..c2e397f --- /dev/null +++ b/blog/how-send-email-nim-2019-08-28.markdown @@ -0,0 +1,135 @@ +--- +title: How to Send Email with Nim +date: 2019-08-28 +--- + +# How to Send Email with Nim + +Nim offers an [smtp][nimsmtp] module, but it is a bit annoying to use out of the +box. This blogpost hopes to be a mini-tutorial on the basics of how to use the +smtp library and give developers best practices for handling outgoing email in +ways that Google or iCloud will accept. + +## SMTP in a Nutshell + +[SMTP][SMTPrfc], or the Simple Mail Transfer Protocol is the backbone of how +email works. It's a very simple line-based protocol, and there are wrappers for +it in almost every programming language. Usage is pretty simple: + +- The client connects to the server +- The client authenticates itself with the server +- The client signals that it would like to create an outgoing message to the server +- The client sends the raw contents of the message to the server +- The client ends the message +- The client disconnects + +Unfortunately, the devil is truly in the details here. There are a few things +that _absolutely must_ be present in your emails in order for services like +GMail to accept them. They are: + +- The `From` header specifying where the message was sent from +- The Mime-Version that your code is using (if you aren't sure, put `1.0` here) +- The Content-Type that your code is sending to users (probably `text/plain`) + +For a more complete example, let's create a `Mailer` type and a constructor: + +```nim +# mailer.nim +import asyncdispatch, logging, smtp, strformat, strutils + +type Mailer* = object + address: string + port: Port + myAddress: string + myName: string + username: string + password: string + +proc newMailer*(address, port, myAddress, myName, username, password: string): Mailer = + result = Mailer( + address: address, + port: port.parseInt.Port, + myAddress: myAddress, + myName: myName, + username: username, + password: password, + ) +``` + +And let's write a `mail` method to send out email: + +```nim +proc mail(m: Mailer, to, toName, subject, body: string) {.async.} = + let + toList = @[fmt"{toName} <{to}>"] + msg = createMessage(subject, body, toList, @[], [ + ("From", fmt"{m.myName} <{m.myAddress}"), + ("MIME-Version", "1.0"), + ("Content-Type", "text/plain"), + ]) + + var client = newAsyncSmtp(useSsl = true) + await client.connect(m.address, m.port) + await client.auth(m.username, m.password) + await client.sendMail(m.myAddress, toList, $msg) + info "sent email to: ", to, " about: ", subject + await client.close() +``` + +Breaking this down, you can clearly see the parts of the SMTP connection as I +laid out before. The `Mailer` creates a new transient SMTP connection, +authenticates with the remote server, sends the properly formatted email to +the server and then closes the connection cleanly. + +If you want to test this code, I suggest testing it with a freely available +email provider that offers TLS/SSL-encrypted SMTP support. This also means that +you need to compile this code with `--define: ssl`, so create `config.nims` and +add the following: + +```nimscript +--define: ssl +``` + +Here's a little wrapper using [cligen][cligen]: + +```nim +when isMailModule: + import cligen, os + + let + smtpAddress = getEnv("SMTP_ADDRESS") + smtpPort = getEnv("SMTP_PORT") + smtpMyAddress = getEnv("SMTP_MY_ADDRESS") + smtpMyName = getEnv("SMTP_MY_NAME") + smtpUsername = getEnv("SMTP_USERNAME") + smtpPassword = getEnv("SMTP_PASSWORD") + + proc sendAnEmail(to, toName, subject, body: string) = + let m = newMailer(smtpAddress, smtpPort, smtpMyAddress, smtpMyName, smtpUsername, smtpPassword) + waitFor m.mail(to, toName, subject, body) + + dispatch(sendAnEmail) +``` + +Usage is simple: + +```console +$ nim c -r mailer.nim --help +Usage: + sendAnEmail [required&optional-params] +Options(opt-arg sep :|=|spc): + -h, --help print this cligen-erated help + --help-syntax advanced: prepend,plurals,.. + -t=, --to= string REQUIRED set to + --toName= string REQUIRED set toName + -s=, --subject= string REQUIRED set subject + -b=, --body= string REQUIRED set body +``` + +I hope this helps, this module is going to be used in my future post on how to +create an application using Nim's [Jester][jester] framework. + +[nimsmtp]: https://nim-lang.org/docs/smtp.html +[SMTPrfc]: https://tools.ietf.org/html/rfc5321 +[jester]: https://github.com/dom96/jester +[cligen]: https://github.com/c-blake/cligen