How to Block Disposable Email Providers in Node.js?

If you’re running a successful consumer-facing app or a business, abuse will start biting you sooner or later. If you’ve a sign-up screen, this takes the form of using disposable email providers. Google “temp email” and you’ll come across thousands of websites providing free temporary emails. Granted, there could be a genuine reason to use these services—privacy, for eg, but more often than not, temporary emails are exploited by abusers and freeloaders.

If are running a SaaS, and have a free plan, this takes the form of abusers creating tens of accounts with temporary emails to exploit the free plan.

Here are a few ways you block these emails. 0

Blocking With a Package

There are various packages that maintain a list of disposable emails and offer a handy API to detect them. disposable-email-domains is a popular one that has a decent maintenance history.

You can use it to verify the email’s domain like this during the sign-up process:

const disposableEmailDomains = require('disposable-email-domains');

function isDisposableEmail(email) {
  const domain = email.split("@")[1];
  return disposableEmailDomains.includes(domain);
}

if (isDisposableEmail(user.email)) {
  return res.status(400).json({
    error: "Disposable email addresses are not allowed"
  });
}

saveUserToDb(user);
res.json({ message: "User registered successfully" });

This is a simple and straightforward method, but there are a couple of problems with this approach:

  1. You’re entirely reliant on the package’s author effort to maintain the list.
  2. You can’t add the domains you found abusing to the list.
  3. Keeping a huge file loaded in memory has a needless cost.

To deal with the first two drawbacks, you can copy the list locally and not rely on the package. If you find a new domain, you can commit a new change to the list.

Blocking Using a Database

Even keeping the list locally has its own set of issues. To update an email, you’ve to go through the whole deployment cycle. This can be easily solved by keeping a list in your own database and verifying the email using a query.

Here’s how you can do it with Postgres. First, we need to define the table. This would only contain a single column called domain.

CREATE TABLE disposable_domains(
  domain TEXT PRIMARY KEY
);

Next, we can import disposable emails into this table using Postgres’ COPY command.

psql "$DB_STRING" -c "\COPY disposable_domains(domain) \
  FROM './domains.csv' \
  DELIMITER ',' \
  CSV HEADER;
"

You can get domains.csv from here.

You can query if the domain exists in that table using this:

SELECT 1 FROM split_part(email, '@', 2) IN (SELECT domain from disposable_domains);

Combining the query with the Node.js code:

async function isDisposableEmail(email) {
  const result = await db.query(`
    SELECT 1 FROM split_part(?, '@', 2) IN (
      SELECT domain from disposable_domains
    )
  `, [email]);

  if (result) {
    return true;
  }

  return false
}

if (await isDisposableEmail(user.email)) {
  return res.status(400).json({
    error: "Disposable email addresses are not allowed"
  });
}

Blocking Using an External Service

Blocking with an external service is an excellent alternative if you don’t want to rely on a stale list or want to get more details than just whether it’s disposable or not.

One such service Mailgun’s verify. It allows you to send a request and get details about the address in return.

Eg:

curl --user 'api:PRIVATE_API_KEY' -X POST \
    https://api.mailgun.net/v4/address/validate \
    -F address='[email protected]'

{
    "address": "[email protected]",
    "is_disposable_address": false,
    "is_role_address": false,
    "reason": [mailbox_does_not_exist],
    "result": "undeliverable",
    "risk": "high"
}

To use Mailgun’s Verify API, you can use axios:

const axios = require('axios');

const isDisposableEmail = async (email) => {
  const apiKey = 'MAILGUN_API_KEY';
  
  const result = await axios({
    method: 'GET',
    url: 'https://api.mailgun.net/v3/address/validate',
    params: {
      address: email
    },
    auth: {
      username: 'api',
      password: apiKey || ''
    }
  });

  if (result.is_disposable_address) {
    return true;
  }

  return false;
}

if (await isDisposableEmail(user.email)) {
  return res.status(400).json({
    error: "Disposable email addresses are not allowed"
  });
} 

Conclusion

Few might say that blocking disposable email addresses is neither a full-proof solution nor ethically correct from a privacy standpoint. Both statements are correct, but when running a business, it’s important to think in terms of practical benefits. If blocking disposable email addresses rarely blocks genuine users, and reasonably restricts abusive users, there’s no reason why you shouldn’t.