Validating email flows is a crucial component of a robust user onboarding and engagement strategy. As a senior developer and architect, I often encounter scenarios where projects have strict budget constraints, yet require reliable validation mechanisms to ensure email deliverability and proper flow functioning. Leveraging TypeScript's capabilities combined with open-source tools, it is possible to construct an effective, scalable validation process without incurring additional costs.
The Challenge: Validating Email Flows Without a Budget
In many startups or small teams, the lack of dedicated testing services like SendGrid's validation API or Mailgun's email validation APIs can be a challenge. The goal here is to create a validation pipeline that checks email syntax, domain existence, MX record presence, and even simulate email delivery responses—all within free or open-source resources.
Core Validation Steps
- Syntax Checking — Ensuring the email address conforms to standard formats.
- Domain Validation — Verifying the domain exists and has valid DNS records.
- MX Record Validation — Confirming the domain has mail exchange records to accept emails.
- Simulated Mailbox Testing — Checking if the email address is deliverable.
While the first three can be managed through DNS lookups and regex parsing in TypeScript, the mailbox testing can be simulated via SMTP commands.
Implementing the Validation in TypeScript
Below is a comprehensive example of how to implement these steps using pure TypeScript and free public DNS/SMTP libraries.
import * as dns from 'dns';
import * as net from 'net';
// Step 1: Syntax Validation
function isValidEmailSyntax(email: string): boolean {
const emailRegex = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
return emailRegex.test(email);
}
// Step 2 & 3: Domain & MX Record Validation
function checkDomainRecords(domain: string): Promise<boolean> {
return new Promise((resolve, reject) => {
dns.resolveMx(domain, (err, addresses) => {
if (err || addresses.length === 0) {
resolve(false);
} else {
resolve(true);
}
});
// Also check if the domain exists
dns.resolve(domain, (err) => {
if (err) {
resolve(false);
}
});
});
}
// Step 4: SMTP Validation (Simulated)
function validateMailbox(email: string, mxHost: string): Promise<boolean> {
return new Promise((resolve) => {
const socket = net.createConnection(25, mxHost);
socket.setEncoding('utf8');
let responseBuffer = '';
socket.on('data', (data) => {
responseBuffer += data;
if (responseBuffer.endsWith('\n')) {
if (responseBuffer.startsWith('220')) {
// Initiate SMTP conversation
socket.write(`HELO localhost\r\n`);
} else if (responseBuffer.startsWith('250')) {
// Proceed to MAIL FROM
socket.write(`MAIL FROM:<survey@domain.com>\r\n`);
} else if (responseBuffer.startsWith('250')) {
// Proceed to RCPT TO
socket.write(`RCPT TO:<${email}>\r\n`);
} else if (responseBuffer.startsWith('550') || responseBuffer.startsWith('553')) {
// Email address rejected
socket.end();
resolve(false);
} else if (responseBuffer.startsWith('250')) {
// Email accepted
socket.end();
resolve(true);
}
responseBuffer = '';
}
});
socket.on('error', () => {
resolve(false); // Unable to connect or validate
});
socket.on('end', () => {
// Connection closed
});
});
}
// Main validation function
async function validateEmail(email: string): Promise<void> {
const [localPart, domain] = email.split('@');
if (!isValidEmailSyntax(email)) {
console.log('Invalid email syntax');
return;
}
const domainExists = await checkDomainRecords(domain);
if (!domainExists) {
console.log('Domain does not exist or has no MX records');
return;
}
const mxRecords = await new Promise<string[]>((resolve) => {
dns.resolveMx(domain, (err, addresses) => {
resolve(err ? [] : addresses.map((mx) => mx.exchange));
});
});
if (mxRecords.length === 0) {
console.log('No MX records found for domain');
return;
}
const mailboxValid = await validateMailbox(email, mxRecords[0]);
if (mailboxValid) {
console.log(`Email (${email}) is valid and deliverable.`);
} else {
console.log(`Email (${email}) appears invalid or undeliverable.`);
}
}
// Example Usage
validateEmail('test@example.com');
Considerations and Limitations
While this approach leverages free tools and TypeScript, it poses some limitations. SMTP validation may get blocked or throttled by email servers, especially if done at scale. DNS lookups are reliable but may not catch all invalid emails, especially with catch-all domains. Moreover, SMTP validation is not foolproof; some domains accept all emails regardless of mailbox validity.
Conclusion
By combining regex validation, DNS MX record lookup, and SMTP interaction, you can implement a comprehensive email validation flow at zero cost. This approach is particularly suitable for early-stage projects or small teams operating on tight budgets, providing a reliable foundation for email flow validation that can scale or integrate with paid services as needed.
Continually refine your validation logic and monitor its effectiveness, especially as email infrastructure and spam filters evolve. With TypeScript and open-source tools, robust email validation is entirely achievable without financial investment, enabling better engagement and improved data quality.
🛠️ QA Tip
Pro Tip: Use TempoMail USA for generating disposable test accounts.
Top comments (0)