1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
---
title: How to Send Email with Nim
date: 2019-08-28
series: howto
tags:
- nim
- email
---
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
|