The Erlang SMTP client and server library.
Provide a generic Erlang SMTP server framework that can be extended via callback modules in the OTP style. A pure Erlang SMTP client is also included. The goal is to make it easy to send and receive email in Erlang without the hassle of POP/IMAP. This is not a complete mailserver - although it includes most of the parts you'd need to build one.
The SMTP server/client supports PLAIN, LOGIN, CRAM-MD5 authentication as well as STARTTLS and SSL (port 465).
Also included is a MIME encoder/decoder, sorta according to RFC204{5,6,7}.
IPv6 is also supported (at least serverside).
SMTP server uses ranch as socket acceptor. It can use Ranch 1.8+, as well as 2.x.
I (Vagabond) have had a simple gen_smtp based SMTP server receiving and parsing copies of all my email for several months and its been able to handle over 100 thousand emails without leaking any RAM or crashing the erlang virtual machine.
- Andrew Thompson (andrew AT hijacked.us)
- Jack Danger Canty (code AT jackcanty.com)
- Micah Warren (micahw AT lordnull.com)
- Arjan Scherpenisse (arjan AT botsquad.com)
- Marc Worrell (marc AT worrell.nl)
- gen_smtp is used to provide the email functionality of OpenACD
- gen_smtp is used as both the SMTP server and SMTP client for Zotonic
- Chicago Boss uses gen_smtp for its mail API.
- Gmailbox uses gen_smtp to provide a free email forwarding service.
- JOSHMARTIN GmbH uses gen_smtp to send emails in Hygeia to send emails for contact tracing of SARS-CoV-2.
- many libraries depend on gen_smtp according to hex.pm
If you'd like to share your usage of gen_smtp, please submit a PR to this README.md
.
Here's an example usage of the client:
gen_smtp_client:send({"whatever@test.com", ["andrew@hijacked.us"],
"Subject: testing\r\nFrom: Andrew Thompson <andrew@hijacked.us>\r\nTo: Some Dude <foo@bar.com>\r\n\r\nThis is the email body"},
[{relay, "smtp.gmail.com"}, {username, "me@gmail.com"}, {password, "mypassword"}]).
The From and To addresses will be wrapped in <>
if they aren't already,
TLS will be auto-negotiated if available (unless you pass {tls, never}
) and
authentication will by attempted by default since a username/password were
specified ({auth, never}
overrides this).
If you want to mandate tls or auth, you can pass {tls, always}
or {auth, always}
as one of the options. You can specify an alternate port with {port, 2525}
(default is 25) or you can indicate that the server is listening for SSL
connections using {ssl, true}
(port defaults to 465 with this option).
send(Email, Options)
send(Email, Options, Callback)
send_blocking(Email, Options)
The send
method variants send/2, send/3, send_blocking/2
take an Options
argument.
Options
must be a proplist with the following valid values:
- relay the smtp relay, e.g.
"smtp.gmail.com"
- username the username of the smtp relay e.g.
"me@gmail.com"
- password the password of the smtp relay e.g.
"mypassword"
- auth whether the smtp server needs authentication. Valid values are
if_available
,always
, andnever
. Defaults toif_available
. If your smtp relay requires authentication set it toalways
- ssl whether to connect on 465 in ssl mode. Defaults to
false
- tls valid values are
always
,never
,if_available
. Most modern smtp relays use tls, so set this toalways
. Defaults toif_available
- tls_options used in
ssl:connect
, More info at Erlang documentation - ssl. Defaults to[{versions , ['tlsv1', 'tlsv1.1', 'tlsv1.2']}]
. This is merged with options listed at: smtp_socket.erl#L50 - SSL_CONNECT_OPTIONS . Any options not present in this list will be ignored. - hostname the hostname to be used by the smtp relay. Defaults to:
smtp_util:guess_FQDN()
. The hostname on your computer might not be correct, so set this to a valid value. - retries how many retries per smtp host on temporary failure. Defaults to 1, which means it will retry once if there is a failure.
- protocol valid values are
smtp
,lmtp
. Default issmtp
You may wish to configure DKIM signing RFC6376 or RFC8463 (Ed25519) of outgoing emails for better security. To do that you need public and private keys, which can be generated by following commands:
# RSA
openssl genrsa -out private-key.pem 1024
openssl rsa -in private-key.pem -out public-key.pem -pubout
# Ed25519 - Erlang/OTP 24.1+ only!
openssl genpkey -algorithm ed25519 -out private-key.pem
openssl pkey -in private-key.pem -pubout -out public-key.pem
# DKIM DNS record p value for Ed25519 must only contain Base64 encoded public key, without ASN.1
openssl asn1parse -in public-key.pem -offset 12 -noout -out /dev/stdout | openssl base64
To send DKIM-signed email:
{ok, PrivKey} = file:read_file("private-key.pem"),
DKIMOptions = [
{s, <<"foo.bar">>},
{d, <<"example.com">>},
{private_key, {pem_plain, PrivKey}}]}
%{private_key, {pem_encrypted, EncryptedPrivKey, "password"}}
],
SignedMailBody = \
mimemail:encode({<<"text">>, <<"plain">>,
[{<<"Subject">>, <<"DKIM testing">>},
{<<"From">>, <<"Andrew Thompson <andrew@hijacked.us>">>},
{<<"To">>, <<"Some Dude <foo@bar.com>">>}],
#{},
<<"This is the email body">>},
[{dkim, DKIMOptions}]),
gen_smtp_client:send({"whatever@example.com", ["andrew@hijacked.us"], SignedMailBody}, []).
For using Ed25519 you need to set the option {a, 'ed25519-sha256'}
.
Don't forget to put your public key to foo.bar._domainkey.example.com
TXT DNS record as something like
RSA:
v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBA......
Ed25519:
v=DKIM1; g=*; k=ed25519; p=MIGfMA0GCSqGSIb3DQEBA......
See RFC6376 for more details.
gen_smtp
ships with a simple callback server example, smtp_server_example
. To start the SMTP server with this as the callback module, issue the following command:
gen_smtp_server:start(smtp_server_example).
gen_smtp_server starting at nonode@nohost
listening on {0,0,0,0}:2525 via tcp
{ok,<0.33.0>}
By default it listens on 0.0.0.0 port 2525. You can telnet to it and test it:
^andrew@orz-dashes:: telnet localhost 2525 [~]
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 localhost ESMTP smtp_server_example
EHLO example.com
250-orz-dashes
250-SIZE 10485670
250-8BITMIME
250-PIPELINING
250 WTF
MAIL FROM: andrew@hijacked.us
250 sender Ok
RCPT TO: andrew@hijacked.us
250 recipient Ok
DATA
354 enter mail, end with line containing only '.'
Good evening gentlemen, all your base are belong to us.
.
250 queued as #Ref<0.0.0.47>
QUIT
221 Bye
Connection closed by foreign host.