# Verifying an Email Address PHP

It doesn't take much experience with email to discover what happens when it is misaddressed. The email is returned to you. This is called bounced email. Consider for a moment a Web site that allows users to fill out a form that includes an email address and sends a thank-you message. Certainly many people will either mistakenly mistype their addresses or purposely give a bad address. You can check the form of the address, of course, but a well-formed address can fail to match to a real mail box. When this happens, the mail bounces back to the user who sent the mail. Unfortunately, this is probably the Web server itself.

Reading through the bounced email can be interesting. Those running an e-commerce site may be concerned about order confirmations that go undelivered. Yet, the volume of mail can be very large. Add to this that delivery failure is not immediate. To the process that sends the mail, it appears to be successful. It may be worthwhile to verify an email address before sending mail.

RFC 821 describes the SMTP protocol, which is used for exchanging email. You can read it at the faqs.It lives up to its name, simple mail transfer protocol, in that it's simple enough to use interactively from a telnet session. In order to verify an address, you can connect to the appropriate SMTP server and begin sending a message. If you specify a valid recipient, the server will return a 250 response code, at which point you can abort the process. It sounds easy, but there's a catch. The domain name portion of an address, the part after the @, is not necessarily the same machine that receives email. Domains are associated with one or more mail exchangers—machines that accept STMP connections for delivery of local mail. The getmxrr function returns all DNS records for a given domain.

The verifyEmail function is based on a similar function written by Jon Stevens. As you can see, the function attempts to fetch a list of mail exchangers. If a domain doesn't have mail exchangers, the script guesses that the domain name itself accepts mail.

<?
/*
** Function: verifyEmail
** Input: STRING address, REFERENCE error
** Output: BOOLEAN
** Description: Attempts to verify an email address by
** contacting a mail exchanger. Registered mail
** exchangers are requested from the domain controller
first,
** then the exact domain itself. The error argument will
** contain relevant text if the address could not be
** verified.
*/
function verifyEmail($address, &$error)
{
global $SERVER_NAME; list($user, $domain) = split("@",$address, 2);
//make sure the domain has a mail exchanger
if(checkdnsrr($domain, "MX")) { //get mail exchanger records if(!getmxrr($domain, $mxhost,$mxweight))
{
$error = "Could not retrieve mail exchangers!<BR> "; return(FALSE); } } else { //if no mail exchanger, maybe the host itself //will accept mail$mxhost[] = $domain;$mxweight[] = 1;
}
//create sorted array of hosts
for($i = 0;$i count($mxhost);$i++)
{
$weighted_host[($mxweight[$i])] =$mxhost[$i]; } ksort($weighted_host);
//loop over each host
foreach($weighted_host as$host)
{
//connect to host on SMTP port
if(!($fp = fsockopen($host, 25)))
{
//couldn't connect to this host, but
//the next might work
continue;
}
/*
** skip over 220 messages
** give up if no response for 10 seconds
*/
set_socket_blocking($fp, FALSE);$stopTime = time() + 10;
$gotResponse = FALSE; while(TRUE) { //try to get a line from mail server$line = fgets($fp, 1024); if(substr($line, 0, 3) == "220")
{
//reset timer
$stopTime = time() + 10;$gotResponse = TRUE;
}
elseif(($line == "") AND ($gotResponse))
{
break;
}
elseif(time() > $stopTime) { break; } } if(!$gotResponse)
{
//this host was unresponsive, but
//maybe the next will be better
continue;
}
set_socket_blocking ($fp, TRUE); //sign in fputs($fp, "HELO $SERVER_NAME "); fgets($fp, 1024);
//set from
fputs($fp, "MAIL FROM: <info@$domain> ");
fgets($fp, 1024); //try address fputs($fp, "RCPT TO: <$address> ");$line = fgets($fp, 1024); //close connection fputs($fp, "QUIT ");
fclose($fp); if(substr($line, 0, 3) != "250")
{
//mail server doesn't recognize
$error =$line;
return(FALSE);
}
else
{
return(TRUE);
}
}
$error = "Unable to reach a mail exchanger!"; return(FALSE); } if(verifyEmail("leon@clearink.com", &$error))
{
print("Verified!<BR> ");
}
else
{
print("Could not verify!<BR> ");
print("Error: \$error<BR> ");
}
?>

SMTP servers precede each message with a numerical code, such as the 250 code mentioned above. When first connecting with a server, any number of 220 messages are sent. These contain comments, such as the AOL servers' reminders not to use them for spam. No special code marks the end of the comments; the server simply stops sending lines. Recall that by default the fgets function returns after encountering the maximum number of characters specified or an end-of-line marker. This will not work in the case of an indeterminate number of lines. The script will wait forever after the last comment. Socket blocking must be turned off to handle this situation.

When set_socket_blocking turns off blocking, fgets returns immediately with whatever data is available in the buffer. The strategy is to loop continually, checking the buffer each time through the loop. There will likely be some lag time between establishing a connection and receiving the first message from the server. Then, as 220 messages appear, the script must begin watching for the data to stop flowing, which means the server is likely waiting for a command. To avoid the situation where a server is very unresponsive, a further check must be made against a clock. If ten seconds pass, the server will be considered unavailable.