Are these emails valid? Link to heading

I have a list of about 100K emails. Before I just start sending them off I wanted to check if they are valid. I decided to check their domain names to see if they have MX records at all.

Here is the usage example:

# Usage:
#   ./checkmx.sh [-p N] [@dnsserver] [file]
#
# Examples:
#   ./checkmx.sh domains.txt
#   ./checkmx.sh -p 10 @8.8.8.8 domains.txt
#   cat domains.txt | ./checkmx.sh @1.1.1.1
#
# Output:
#   stdout = domains that HAVE MX records
#   stderr = domains that LACK MX records

Now I was an idiot and forgot to point it at 8.8.8.8 or 1.1.1.1 so my name server was the first hop… but it riped through about 20k domains in a few mins.

I ran it redirecting the different outputs like this:

$ ./checkmx.sh domains.txt > domains_yes_mx.txt 2> domains_no_mx.txt

Instead of:

$ ./checkmx.sh @8.8.8.8 domains.txt > domains_yes_mx.txt 2> domains_no_mx.txt

In case it helps someone else… enjoy.

#!/usr/bin/env bash
# checkmx.sh
#
# Usage:
#   ./checkmx.sh [-p N] [@dnsserver] [file]
#
# Examples:
#   ./checkmx.sh domains.txt
#   ./checkmx.sh -p 10 @8.8.8.8 domains.txt
#   cat domains.txt | ./checkmx.sh @1.1.1.1
#
# Output:
#   stdout = domains that HAVE MX records
#   stderr = domains that LACK MX records

set -euo pipefail

dns_server=""
file=""
parallelism=4   # default parallel jobs

# Parse arguments
while [[ $# -gt 0 ]]; do
    case "$1" in
        -p)
            if [[ $# -lt 2 ]]; then
                echo "Error: -p requires a number" >&2
                exit 1
            fi
            parallelism="$2"
            shift 2
            ;;
        @*)
            dns_server="$1"
            shift
            ;;
        -*)
            echo "Unknown option: $1" >&2
            exit 1
            ;;
        *)
            file="$1"
            shift
            ;;
    esac
done

# Input: file or stdin
if [[ -n "$file" ]]; then
    input=$(cat "$file")
else
    input=$(cat -)
fi

# Function to check a single domain
check_domain() {
    local domain="$1"
    local dns_server="$2"

    [[ -z "$domain" ]] && return 0

    # Query MX, suppress dig warnings/errors
    if dig $dns_server +short MX "$domain" 2>/dev/null | grep -q .; then
        echo "$domain"          # HAS MX -> stdout
    else
        echo "$domain" >&2      # NO MX  -> stderr
    fi
}

export -f check_domain
export dns_server

# Run in parallel
printf "%s\n" "$input" | \
    xargs -P "$parallelism" -n 1 bash -c 'check_domain "$0" "$dns_server"' 

# end