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:

  1. "name@example.de"
  2. "<name@example.de>"
  3. "NAME <name@example.de>"
  4. {mail: "name@example.de"}
  5. {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

  1. from, replyTo we only allow a “MailString”.
  2. 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:

  1. return true the E-Mail is keept in the list
  2. return false the E-Mail is removed from the list
  3. 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!
});

TLS issues

When getting TLS errors make shure:

  1. you use the correct port (mostly 25, 587, 465)
  2. the server supports STARTTLS when using client.connect
  3. the server supports TLS when using client.connectTLS
  4. Use the command openssl s_client -debug -starttls smtp -crlf -connect your-host.de:587 or openssl 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.
  5. Feel free to create issues if you are ok with that share the port and host so a proper debug can be done.
  6. We can only support TLS where Deno supports it and Deno uses rustls wich explicitly not implemented some “weak” ciphers.