Deno SMTP mail client
IMPORTANT SECURITY INFORMATION
PLEASE update to a version >= 0.8! 0.8 has a problem where malformed mails could potentialy allow attackers to create a mail (with linebreaks) to send unwanted SMTP commands. This could result in authentic phishing attacks! With no way for the user to identify that this is a phishing mail! Or that this mail contains a dangerous attachment!
Also make sure that Mails are sent one after the other as they can corrupt each others data!
Allowed Mail Formats
A single Mail mail@example.de
with a name NAME
can be encoded in the
following ways:
"name@example.de"
"<name@example.de>"
"NAME <name@example.de>"
{mail: "name@example.de"}
{mail: "name@example.de", name: "NAME"}
Where 1-3 is called a “MailString”.
Multiple Mails can be an Array of the above OR a object that maps names to mails for example:
{"P1": "p1@example.de", "P2": "p2@example.de"}
we call this a MailObject.
For the fields
from
,replyTo
we only allow a “MailString”.to
,cc
,bcc
we allow a MailObject a Array of single Mails or a single Mail.
Sending multiple mails
Note that for race-condition reasons we can’t send multiple mails at once.
Because of that if send is already called and still processing a mail
client.send
will queue that sending.
Example
import { SmtpClient, quotedPrintableEncode } from "https://deno.land/x/denomailer/mod.ts";
const client = new SmtpClient();
await client.connect({
hostname: "smtp.163.com",
port: 25,
username: "username",
password: "password",
});
await client.send({
from: "mailaddress@163.com",
to: "Me <to-address@xx.com>",
cc: [
"name@example.de",
"<name@example.de>",
"NAME <name@example.de>",
{mail: "name@example.de"},
{mail: "name@example.de", name: "NAME"}
],
bcc: {
"Me": "to-address@xx.com"
},
subject: "Mail Title",
content: "Mail Content",
html: "<a href='https://github.com'>Github</a>",
date: "12 Mar 2022 10:38:05 GMT",
priority: "high",
replyTo: 'mailaddress@163.com',
attachments: [
{ encoding: "text"; content: 'Hi', contentType: 'text/plain', filename: 'text.txt' },
{ encoding: "base64"; content: '45dasjZ==', contentType: 'image/png', filename: 'img.png' },
{
content: new Uint8Array([0,244,123]),
encoding: "binary",
contentType: 'image/jpeg',
filename: 'bin.png'
}
],
mimeContent: [
{
mimeType: 'application/markdown',
content: quotedPrintableEncode('# Title\n\nHello World!'),
transferEncoding: 'quoted-printable'
}
]
});
await client.close();
TLS connection
await client.connectTLS({
hostname: "smtp.163.com",
port: 465,
username: "username",
password: "password",
});
Use in Gmail
await client.connectTLS({
hostname: "smtp.gmail.com",
port: 465,
username: "your username",
password: "your password",
});
await client.send({
from: "someone@163.com", // Your Email address
to: "someone@xx.com", // Email address of the destination
subject: "Mail Title",
content: "Mail Content,maybe HTML",
});
await client.close();
Filter E-Mails
If you want a custom E-Mail validator and filter some E-Mails (because they are burner mails or the domain is on a blacklist or only allow specific domains etc.) you can add the mailFilter
option to the smtp-client constructor options. mailFilter
takes a function that gets 3 Arguments the “mailbox” (all that is before @ in the mail), the “domain” (what is after the @) and internalTag
that is a new option that can be set in the mailConfig so you can set a type for that mail for example type newsletter
etc. internalTag
can be a string
or a symbol
.
The filter function returns a boolean or a Promise that resolves to a boolean. There are 3 things you can do when this function is called:
- return
true
the E-Mail is keept in the list - return
false
the E-Mail is removed from the list - throw an Error the E-Mail is aborted and never send
So you can decide if a single mail error results in a complete mail abort or it only get removed from the list.
You can for example validate against this list: https://github.com/wesbos/burner-email-providers.
Configuring your client
You can pass options to your client through the SmtpClient
constructor.
import { SmtpClient } from "https://deno.land/x/denomailer/mod.ts";
//Defaults
const client = new SmtpClient({
console_debug: true, // enable debugging this is good while developing should be false in production as Authentication IS LOGGED TO CONSOLE!
unsecure: true, // allow unsecure connection to send authentication IN PLAIN TEXT and also mail content!
});
Pool, Worker
This is unstable API may change! This requires deno to run in unstable mode.
Adds 2 new classes SMTPWorker
and SMTPWorkerPool
(for constructor options see code for now). This creates a SMTP client (or multiple) that get automaticly killed if the connection is not used for around 60s.
TLS issues
When getting TLS errors make shure:
- you use the correct port (mostly 25, 587, 465)
- the server supports STARTTLS when using
client.connect
- the server supports TLS when using
client.connectTLS
- Use the command
openssl s_client -debug -starttls smtp -crlf -connect your-host.de:587
oropenssl s_client -debug -crlf -connect your-host.de:587
and get the used cipher this should be a cipher with “forward secrecy”. Check the status of the cipher on https://ciphersuite.info/cs/ . If the cipher is not STRONG this is an issue with your mail provider so you have to contact them to fix it. - Feel free to create issues if you are ok with that share the port and host so a proper debug can be done.
- We can only support TLS where Deno supports it and Deno uses rustls wich explicitly not implemented some “weak” ciphers.
Non SpecCompliant SMTP-Server
There are some SMTP-Server that don’t follow the spec to 100%. This can result in unexpected errors in denomailer. If this happens (for example in https://github.com/EC-Nordbund/denomailer/blob/03a66a6f9a4b5f349ea35856f5903fb45fd0cc5f/smtp.ts#L376 the server sends a 250) please create an issue. We will try and do the following:
- Check if it is not an error in denomailer
- Try to fix it at the SMTP-Server side (create an issue if the server is an opensource project etc.)
- We will add a temporary workaround by changing denomailer. This will include log messages telling the developer (if the workaround is used) that denomailer used the workaround wich can be removed at any time.