4.3 KiB
title | date | series | tags | ||
---|---|---|---|---|---|
How to Send Email with Nim | 2019-08-28 | howto |
|
Nim offers an smtp 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, 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:
# 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:
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:
--define: ssl
Here's a little wrapper using cligen:
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:
$ 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 framework.