新项目,有需要提前“判断电子邮件地址是否真实存在”。

首先想到这是一个标准化问题,网上肯定有参考答案了。

大概思路是,发一封邮件给这个账户,或者通过SMTP等协议进行通信。

邮箱几十亿,不可能有简单的API,直接判断是否有效,不然全网等可用邮箱都被你给拿到了。

 

简单做个汇总,备忘。

实践结论:QQ、163等标准化邮箱,可以判断是否存在。部分企业邮箱,如果使用的是腾讯企业邮箱,也可以。

部分企业邮箱,京东等,不太行。

微软Hotmail邮箱,返回超时。

 

在线检测工具,比如 https://verify-email.org/ 感觉不靠谱。


从了解到放弃。

实际测试的邮箱,已做修改,仅供参考。

 

第1种实践过的,最佳代码:

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.MXRecord;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;


public class MailValidKit {

    public static void main(String[] args) {
        System.out.println("应该为true:"+new MailValidKit().valid("100582783@qq.com", "jootmir.org"));
        System.out.println("应该为true:"+new MailValidKit().valid("fasuion@qq.com", "jootmir.org"));
        System.out.println("应该为false:"+new MailValidKit().valid("fansnion@qq.com", "jootmir.org"));
        System.out.println("应该为true:"+new MailValidKit().valid("lewenans@163.com", "jootmir.org"));
        System.out.println("应该为true:"+new MailValidKit().valid("wen.lei@10cdit.com", "jootmir.org"));
        System.out.println("应该为true:"+new MailValidKit().valid("ein1@jd.com", "jootmir.org"));
        System.out.println("应该为true:"+new MailValidKit().valid("wngongao@jd.com", "jootmir.org"));
    }
    
    /**
     * 验证邮箱是否存在
     * <br>
     * 由于要读取IO,会造成线程阻塞
     * 
     * @param toMail
     *         要验证的邮箱
     * @param domain
     *         发出验证请求的域名(是当前站点的域名,可以任意指定)
     * @return
     *         邮箱是否可达
     */
    public boolean valid(String toMail, String domain) {
        if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
        if(!StringUtils.contains(toMail, '@')) return false;
        String host = toMail.substring(toMail.indexOf('@') + 1);
        if(host.equals(domain)) return false;
        Socket socket = new Socket();
        try {
            // 查找mx记录
            Record[] mxRecords = new Lookup(host, Type.MX).run();
            if(ArrayUtils.isEmpty(mxRecords)) return false;
            // 邮件服务器地址
            String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
            if(mxRecords.length > 1) { // 优先级排序
                List<Record> arrRecords = new ArrayList<Record>();
                Collections.addAll(arrRecords, mxRecords);
                Collections.sort(arrRecords, new Comparator<Record>() {
                    
                    public int compare(Record o1, Record o2) {
                        return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
                    }
                    
                });
                mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
            }
            // 开始smtp
            socket.connect(new InetSocketAddress(mxHost, 25));
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            // 超时时间(毫秒)
            long timeout = 6000;
            // 睡眠时间片段(50毫秒)
            int sleepSect = 50;
            
            // 连接(服务器是否就绪)
            if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
                return false;
            }
            
            // 握手
            bufferedWriter.write("HELO " + domain + "\r\n");
            bufferedWriter.flush();
            if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                return false;
            }
            // 身份
            bufferedWriter.write("MAIL FROM: <check@" + domain + ">\r\n");
            bufferedWriter.flush();
            if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                return false;
            }
            // 验证
            bufferedWriter.write("RCPT TO: <" + toMail + ">\r\n");
            bufferedWriter.flush();
            if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                return false;
            }
            // 断开
            bufferedWriter.write("QUIT\r\n");
            bufferedWriter.flush();
            return true;
        } catch (NumberFormatException e) {
        } catch (TextParseException e) {
        } catch (IOException e) {
        } catch (InterruptedException e) {
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
            }
        }
        return false;
    }
    
    private int getResponseCode(long timeout, int sleepSect, BufferedReader bufferedReader) throws InterruptedException, NumberFormatException, IOException {
        int code = 0;
        for(long i = sleepSect; i < timeout; i += sleepSect) {
            Thread.sleep(sleepSect);
            if(bufferedReader.ready()) {
                String outline = bufferedReader.readLine();
                // FIXME 读完……
                while(bufferedReader.ready())
                    /*System.out.println(*/bufferedReader.readLine()/*)*/;
                /*System.out.println(outline);*/
                code = Integer.parseInt(outline.substring(0, 3));
                break;
            }
        }
        return code;
    }
}

代码主要参考:

 

第2种,还可以,163邮箱无法识别

import java.io.IOException;

import org.apache.commons.net.smtp.SMTPClient;
import org.apache.commons.net.smtp.SMTPReply;
import org.apache.log4j.Logger;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;

/**
 * 
 * 校验邮箱:1、格式是否正确 2、是否真实有效的邮箱地址 
 * 步骤: 1、从dns缓存服务器上查询邮箱域名对应的SMTP服务器地址 2、尝试建立Socket连接
 * 3、尝试发送一条消息给SMTP服务器 4、设置邮件发送者 5、设置邮件接收者 6、检查响应码是否为250(为250则说明这个邮箱地址是真实有效的)
 * 
 * @author Michael Ran 
 *
 */
// 总体靠谱
public class CheckEmailValidityUtil {
	private static final Logger logger = Logger.getLogger(CheckEmailValidityUtil.class);

	/**
	 * @param email
	 *            待校验的邮箱地址
	 * @return
	 */
	public static boolean isEmailValid(String email) {
		if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
			logger.error("邮箱(" + email + ")校验未通过,格式不对!");
			return false;
		}
		String host = "";
		String hostName = email.split("@")[1];
		// Record: A generic DNS resource record. The specific record types
		// extend this class. A record contains a name, type, class, ttl, and
		// rdata.
		Record[] result = null;
		SMTPClient client = new SMTPClient();
		try {
			// 查找DNS缓存服务器上为MX类型的缓存域名信息
			Lookup lookup = new Lookup(hostName, Type.MX);
			lookup.run();
			if (lookup.getResult() != Lookup.SUCCESSFUL) {// 查找失败
				logger.error("邮箱(" + email + ")校验未通过,未找到对应的MX记录!");
				return false;
			} else {// 查找成功
				result = lookup.getAnswers();
			}
			// 尝试和SMTP邮箱服务器建立Socket连接
			for (int i = 0; i < result.length; i++) {
				host = result[i].getAdditionalName().toString();
				logger.info("SMTPClient try connect to host:" + host);

				// 此connect()方法来自SMTPClient的父类:org.apache.commons.net.SocketClient
				// 继承关系结构:org.apache.commons.net.smtp.SMTPClient-->org.apache.commons.net.smtp.SMTP-->org.apache.commons.net.SocketClient
				// Opens a Socket connected to a remote host at the current
				// default port and
				// originating from the current host at a system assigned port.
				// Before returning,
				// _connectAction_() is called to perform connection
				// initialization actions.
				// 尝试Socket连接到SMTP服务器
				client.connect(host);
				// Determine if a reply code is a positive completion
				// response(查看响应码是否正常).
				// All codes beginning with a 2 are positive completion
				// responses(所有以2开头的响应码都是正常的响应).
				// The SMTP server will send a positive completion response on
				// the final successful completion of a command.
				if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
					// 断开socket连接
					client.disconnect();
					continue;
				} else {
					logger.info("找到MX记录:" + hostName);
					logger.info("建立链接成功:" + hostName);
					break;
				}
			}
			logger.info("SMTPClient ReplyString:" + client.getReplyString());
			//网易163,发现是自己等,有能力进行检测,所以最好用真实的
			String emailPrefix = "leiwenfans";//leiwenfans222,可以随便写?
			String emailSuffix = "163.com";
			
			//emailPrefix="fansunion";
			//emailSuffix="qq.com";
			String fromEmail = emailPrefix + "@" + emailSuffix;
			// Login to the SMTP server by sending the HELO command with the
			// given hostname as an argument.
			// Before performing any mail commands, you must first login.
			// 尝试和SMTP服务器建立连接,发送一条消息给SMTP服务器
			client.login(emailPrefix);
			logger.info("SMTPClient login:" + emailPrefix + "...");
			logger.info("SMTPClient ReplyString:" + client.getReplyString());

			// Set the sender of a message using the SMTP MAIL command,
			// specifying a reverse relay path.
			// The sender must be set first before any recipients may be
			// specified,
			// otherwise the mail server will reject your commands.
			// 设置发送者,在设置接受者之前必须要先设置发送者
			client.setSender(fromEmail);
			logger.info("设置发送者 :" + fromEmail);
			logger.info("SMTPClient ReplyString:" + client.getReplyString());

			// Add a recipient for a message using the SMTP RCPT command,
			// specifying a forward relay path. The sender must be set first
			// before any recipients may be specified,
			// otherwise the mail server will reject your commands.
			// 设置接收者,在设置接受者必须先设置发送者,否则SMTP服务器会拒绝你的命令
			client.addRecipient(email);
			logger.info("设置接收者:" + email);
			logger.info("SMTPClient ReplyString:" + client.getReplyString());
			logger.info("SMTPClient ReplyCode:" + client.getReplyCode() + "(250表示正常)");
			if (250 == client.getReplyCode()) {
				return true;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				client.disconnect();
			} catch (IOException e) {
			}
		}
		return false;
	}

	//建立连接可能会非常慢,几十秒
	public static void main(String[] args) {
		//test163();
		testJd();
		//testBrng();
		//testQq();
	
		testHotmail();
	}

	//SMTPClient login:leiwenfans...
	//SMTPClient ReplyString:250 DB5EUR03FT043.mail.protection.outlook.com Hello [36.110.19.90]
	//没下文了 
	private static void testHotmail() { 
		System.out.println("合法hotmail邮箱:"+isEmailValid("fasnion@otmail.com"));
		System.out.println("合法hotmail邮箱:"+isEmailValid("huingans@homail.com"));
	}

	//550
	//设置接收者:liwe@jd.com
	//SMTPClient ReplyString:550 5.1.1 Recipient address rejected: User unknown
	private static void testJd() {
		System.out.println("jd企业邮箱:"+isEmailValid("eien1@jd.com"));
	}

	private static void test163() {
		//163总是false,550、503等异常状态,而不是250
		
		System.out.println("合法163邮箱:"+isEmailValid("liwens@163.com"));
	}

	private static void testQq() {
		//非法格式
		System.out.println("格式不正确:"+isEmailValid("90310930qq.com"));
		System.out.println("格式不正确2:"+isEmailValid("903109360@qqcom"));
		//普通随机邮箱
		System.out.println("普通随机邮箱:"+isEmailValid("903109360@qq.com"));//原作者自己测试等邮箱
		System.out.println("普通随机邮箱,瞎填的:"+isEmailValid("903109360222@qq.com"));
		//我的邮箱,qq
		System.out.println("合法qq数字邮箱:"+isEmailValid("2437088@qq.com"));
		System.out.println("合法qq自定义邮箱:"+isEmailValid("fasunon@qq.com"));
	}

	private static void testBaing() {
		//baron企业邮箱ok
		System.out.println("企业邮箱:"+isEmailValid("wn.li@10edit.com"));
		System.out.println("企业邮箱2:"+isEmailValid("cheong.yu@1dit.com"));
		System.out.println("企业邮箱3:"+isEmailValid("yonao.niu@10edit.com"));
		System.out.println("企业邮箱4:"+isEmailValid("yonghao.@.com"));
		System.out.println("企业邮箱:"+isEmailValid("wen.i123@100crdi"));
	}
}

第3种,不太靠谱,io卡住,源代码中得正则表达式还有问题

import java.io.*;
import java.net.*;
import org.xbill.DNS.*;

// 不太靠谱
public class CheckEmail
{
	public static void main(String[] args) {
		System.out.println("企业邮箱:"+CheckEmail.check("wen.lei@100ct.com"));//check方式有问题
		System.out.println("qq邮箱:"+CheckEmail.check("2403818@qq.com"));//check方式有问题
	}
    public static boolean check(String mailTo)
    {
    	//正则不对
       /* if (!mailTo.matches("w+([-_.]w+)*@w+([-.]w+)*.w+([-.]w+)*")) { //判断格式
            return false;
        }*/
    	if (!mailTo.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
    		System.out.println("邮箱(" + mailTo + ")校验未通过,格式不对!");
			return false;
		}
        String hostName = mailTo.split("@")[1];  //获得如163.com
        String host = null;  // MX记录
        
        try {
            Lookup lookup = new Lookup(hostName, Type.MX);
            lookup.run();
            if (lookup.getResult() != Lookup.SUCCESSFUL) {
                return false;
            }
            else {
                Record[] result = lookup.getAnswers();
                host = result[0].getAdditionalName().toString();
            }
            
            Socket socket = new Socket(host, 25);
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            in.readLine();
            out.write("HELO Sender ");
            out.flush();
            in.readLine();
            out.write("MAIL FROM:<xx@xxx.xx> ");
            out.flush();
            in.readLine();
            out.write("RCPT TO:<" + mailTo + "> ");
            out.flush();
            String r = in.readLine();
            out.write("QUIT ");
            out.flush();
            
            out.close();
            in.close();
            socket.close();
            if (!r.startsWith("250")) {
                return false;
            }
            else {
                return true;
            }
        } catch (Exception e) {
            return false;
        }
    }
}

----------------------以下内容纯copy,备用------------------------------------

1.Java与邮件系统交互之使用Socket验证邮箱是否存在


Java与邮件系统交互之使用Socket验证邮箱是否存在

  最近遇到一个需求:需要验证用户填写的邮箱地址是否真实存在,是否可达。和普通的正则表达式不同,他要求尝试链接目标邮箱服务器并请求校验目标邮箱是否存在。

先来了解


 

  DNS之MX记录

  对于DNS不了解的,请移步百度搜索。

  DNS中除了A记录(域名-IP映射)之外,还有MX记录(邮件交换记录),CNAME记录(别名,咱不管)。

  MX记录就是为了在发送邮件时使用友好域名规则,比如我们发送到QQ邮箱xxx@qq.com。我们填写地址是到“qq.com”,但实际上可能服务器地址千奇百怪/而且许有多个。在设置DNS时可以顺带设置MX记录。

  说白了,“qq.com”只是域名,做HTTP请求响应地址,你邮件能发到Tomcat上吗?那我们发到“qq.com”上面的邮件哪里去了,我们把自己想象成一个邮件服务器,你的用户让你给xxx@qq.com发一封信,你如何操作?找mx记录是必要的。

  SMTP之纯Socket访问

  对于SMTP不了解或Java Socket不了解的,请移步百度搜索。

  邮件协议是匿名协议,我们通过SMTP协议可以让邮件服务器来验证目标地址是否真实存在。

代码实现


 

  由以上介绍可知:通过DNS中MX记录可以找到邮件服务器地址,通过SMTP协议可以让邮件服务器验证目标邮箱地址的真实性。

  那么我们就来进行编码实现。

  首先需要查询DNS,这个需要用到一个Java查询DNS的组件dnsjava(下载),自己写太麻烦。

检验电子邮件地址是否真实存在_apache

 mx

  (上面代码中的生僻类型就是来自dnsjava,我使用apache-commons组件来判断空值和构建排序,return false是在查询失败时。)

  接下来通过优先级排序(mx记录有这个属性)取第一个邮件服务器地址来链接。

  这里的主要代码是通过SMTP发送RCPT TO指令来指定邮件接收方,如果这个地址存在则服务器返回成功状态,如果没有的话则返回错误指令。

1 import java.io.BufferedInputStream;  2 import java.io.BufferedReader;
  3 import java.io.BufferedWriter;
  4 import java.io.IOException;
  5 import java.io.InputStreamReader;
  6 import java.io.OutputStreamWriter;
  7 import java.net.InetSocketAddress;
  8 import java.net.Socket;
  9 import java.util.ArrayList;
 10 import java.util.Collections;
 11 import java.util.Comparator;
 12 import java.util.List;
 13 
 14 import org.apache.commons.lang.ArrayUtils;
 15 import org.apache.commons.lang.StringUtils;
 16 import org.apache.commons.lang.builder.CompareToBuilder;
 17 import org.xbill.DNS.Lookup;
 18 import org.xbill.DNS.MXRecord;
 19 import org.xbill.DNS.Record;
 20 import org.xbill.DNS.TextParseException;
 21 import org.xbill.DNS.Type;
 22 
 23 
 24 public class MailValid {
 25 
 26     public static void main(String[] args) {
 27         System.out.println(new MailValid().valid("100582783@qq.com", "jootmir.org"));
 28     }
 29     
 30     /**
 31      * 验证邮箱是否存在
 32      * <br>
 33      * 由于要读取IO,会造成线程阻塞
 34      * 
 35      * @param toMail
 36      *         要验证的邮箱
 37      * @param domain
 38      *         发出验证请求的域名(是当前站点的域名,可以任意指定)
 39      * @return
 40      *         邮箱是否可达
 41      */
 42     public boolean valid(String toMail, String domain) {
 43         if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
 44         if(!StringUtils.contains(toMail, '@')) return false;
 45         String host = toMail.substring(toMail.indexOf('@') + 1);
 46         if(host.equals(domain)) return false;
 47         Socket socket = new Socket();
 48         try {
 49             // 查找mx记录
 50             Record[] mxRecords = new Lookup(host, Type.MX).run();
 51             if(ArrayUtils.isEmpty(mxRecords)) return false;
 52             // 邮件服务器地址
 53             String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
 54             if(mxRecords.length > 1) { // 优先级排序
 55                 List<Record> arrRecords = new ArrayList<Record>();
 56                 Collections.addAll(arrRecords, mxRecords);
 57                 Collections.sort(arrRecords, new Comparator<Record>() {
 58                     
 59                     public int compare(Record o1, Record o2) {
 60                         return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
 61                     }
 62                     
 63                 });
 64                 mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
 65             }
 66             // 开始smtp
 67             socket.connect(new InetSocketAddress(mxHost, 25));
 68             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
 69             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
 70             // 超时时间(毫秒)
 71             long timeout = 6000;
 72             // 睡眠时间片段(50毫秒)
 73             int sleepSect = 50;
 74             
 75             // 连接(服务器是否就绪)
 76             if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
 77                 return false;
 78             }
 79             
 80             // 握手
 81             bufferedWriter.write("HELO " + domain + "\r\n");
 82             bufferedWriter.flush();
 83             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
 84                 return false;
 85             }
 86             // 身份
 87             bufferedWriter.write("MAIL FROM: <check@" + domain + ">\r\n");
 88             bufferedWriter.flush();
 89             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
 90                 return false;
 91             }
 92             // 验证
 93             bufferedWriter.write("RCPT TO: <" + toMail + ">\r\n");
 94             bufferedWriter.flush();
 95             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
 96                 return false;
 97             }
 98             // 断开
 99             bufferedWriter.write("QUIT\r\n");
100             bufferedWriter.flush();
101             return true;
102         } catch (NumberFormatException e) {
103         } catch (TextParseException e) {
104         } catch (IOException e) {
105         } catch (InterruptedException e) {
106         } finally {
107             try {
108                 socket.close();
109             } catch (IOException e) {
110             }
111         }
112         return false;
113     }
114     
115     private int getResponseCode(long timeout, int sleepSect, BufferedReader bufferedReader) throws InterruptedException, NumberFormatException, IOException {
116         int code = 0;
117         for(long i = sleepSect; i < timeout; i += sleepSect) {
118             Thread.sleep(sleepSect);
119             if(bufferedReader.ready()) {
120                 String outline = bufferedReader.readLine();
121                 // FIXME 读完……
122                 while(bufferedReader.ready())
123                     /*System.out.println(*/bufferedReader.readLine()/*)*/;
124                 /*System.out.println(outline);*/
125                 code = Integer.parseInt(outline.substring(0, 3));
126                 break;
127             }
128         }
129         return code;
130     }
131 }


 

  (解锁和输出123、124行数据可以让你更加清晰SMTP协议)

  对于企业邮箱,可能无法正常验证,这个是因为服务器问题。另外,dnsjava查询DNS是有缓存的。

 

2、Java验证邮箱有效性和真实性


Java验证邮箱是否真实存在有效

要检测邮箱是否真实存在,必须了解两方面知识:
1. MX记录,winodws的nslookup命令。查看学习

2. SMTP协议,如何通过telnet发送邮件。查看学习

有个网站可以校验,http://verify-email.org/, 不过一小时只允许验证10次。

代码如下(补充了一些注释):

 


 

1. import java.io.IOException;
2.  
3. import org.apache.commons.net.smtp.SMTPClient;
4. import org.apache.commons.net.smtp.SMTPReply;
5. import org.apache.log4j.Logger;
6. import org.xbill.DNS.Lookup;
7. import org.xbill.DNS.Record;
8. import org.xbill.DNS.Type;
9.  
10. /**
11.  * 
12.  * 校验邮箱:1、格式是否正确 2、是否真实有效的邮箱地址
13.  * 步骤:
14.  * 1、从dns缓存服务器上查询邮箱域名对应的SMTP服务器地址
15.  * 2、尝试建立Socket连接
16.  * 3、尝试发送一条消息给SMTP服务器
17.  * 4、设置邮件发送者
18.  * 5、设置邮件接收者
19.  * 6、检查响应码是否为250(为250则说明这个邮箱地址是真实有效的)
20.  * @author Michael Ran
21.  *
22.  */
23. public class CheckEmailValidityUtil {
24.     private static final Logger logger = Logger
25.             .getLogger(CheckEmailValidityUtil.class);
26.     /**
27.      * @param email 待校验的邮箱地址
28.      * @return
29.      */
30.     public static boolean isEmailValid(String email) {
31.         if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
32.             logger.error("邮箱("+email+")校验未通过,格式不对!");
33.             return false;
34.         }
35.         String host = "";
36.         String hostName = email.split("@")[1];
37.         //Record: A generic DNS resource record. The specific record types 
38.         //extend this class. A record contains a name, type, class, ttl, and rdata.
39.         Record[] result = null;
40.         SMTPClient client = new SMTPClient();
41.         try {
42.             // 查找DNS缓存服务器上为MX类型的缓存域名信息
43.             Lookup lookup = new Lookup(hostName, Type.MX);
44.             lookup.run();
45.             if (lookup.getResult() != Lookup.SUCCESSFUL) {//查找失败
46.                 logger.error("邮箱("+email+")校验未通过,未找到对应的MX记录!");
47.                 return false;
48.             } else {//查找成功
49.                 result = lookup.getAnswers();
50.             }
51.             //尝试和SMTP邮箱服务器建立Socket连接
52.             for (int i = 0; i < result.length; i++) {
53.                 host = result[i].getAdditionalName().toString();
54.                 logger.info("SMTPClient try connect to host:"+host);
55. 
56.                 //此connect()方法来自SMTPClient的父类:org.apache.commons.net.SocketClient
57.                 //继承关系结构:org.apache.commons.net.smtp.SMTPClient-->org.apache.commons.net.smtp.SMTP-->org.apache.commons.net.SocketClient
58.                 //Opens a Socket connected to a remote host at the current default port and 
59.                 //originating from the current host at a system assigned port. Before returning,
60.                 //_connectAction_() is called to perform connection initialization actions. 
61.                 //尝试Socket连接到SMTP服务器
62.                 client.connect(host);
63.                 //Determine if a reply code is a positive completion response(查看响应码是否正常). 
64.                 //All codes beginning with a 2 are positive completion responses(所有以2开头的响应码都是正常的响应). 
65.                 //The SMTP server will send a positive completion response on the final successful completion of a command. 
66.                 if (!SMTPReply.isPositiveCompletion(client.getReplyCode())) {
67.                     //断开socket连接
68.                     client.disconnect();
69.                     continue;
70.                 } else {
71.                     logger.info("找到MX记录:"+hostName);
72.                     logger.info("建立链接成功:"+hostName);
73.                     break;
74.                 }
75.             }
76.             logger.info("SMTPClient ReplyString:"+client.getReplyString());
77.             String emailSuffix="163.com";
78.             String emailPrefix="ranguisheng";
79.             String fromEmail = emailPrefix+"@"+emailSuffix; 
80.             //Login to the SMTP server by sending the HELO command with the given hostname as an argument. 
81.             //Before performing any mail commands, you must first login. 
82.             //尝试和SMTP服务器建立连接,发送一条消息给SMTP服务器
83.             client.login(emailPrefix);
84.             logger.info("SMTPClient login:"+emailPrefix+"...");
85.             logger.info("SMTPClient ReplyString:"+client.getReplyString());
86. 
87.             //Set the sender of a message using the SMTP MAIL command, 
88.             //specifying a reverse relay path. 
89.             //The sender must be set first before any recipients may be specified, 
90.             //otherwise the mail server will reject your commands. 
91.             //设置发送者,在设置接受者之前必须要先设置发送者
92.             client.setSender(fromEmail);
93.             logger.info("设置发送者 :"+fromEmail);
94.             logger.info("SMTPClient ReplyString:"+client.getReplyString());
95.  
96.             //Add a recipient for a message using the SMTP RCPT command, 
97.             //specifying a forward relay path. The sender must be set first before any recipients may be specified, 
98.             //otherwise the mail server will reject your commands. 
99.             //设置接收者,在设置接受者必须先设置发送者,否则SMTP服务器会拒绝你的命令
100.             client.addRecipient(email);
101.             logger.info("设置接收者:"+email);
102.             logger.info("SMTPClient ReplyString:"+client.getReplyString());
103.             logger.info("SMTPClient ReplyCode:"+client.getReplyCode()+"(250表示正常)");
104.             if (250 == client.getReplyCode()) {
105.                 return true;
106.             }
107.         } catch (Exception e) {
108.             e.printStackTrace();
109.         } finally {
110.             try {
111.                 client.disconnect();
112.             } catch (IOException e) {
113.             }
114.         }
115.         return false;
116.     }
117.     public static void main(String[] args) {
118.         System.out.println(isEmailValid("903109360@qq.com"));
119.     }
120. }

执行结果:

 

MX record about qq.com exists.
 Connection succeeded to mx3.qq.com.
 220 newmx21.qq.com MX QQ Mail Server
 >HELO 163.com
 =250 newmx21.qq.com >MAIL FROM:
 =250 Ok 
 =250 Ok



Outcome: true

如果将被验证的邮箱换为:903109360@qq.con,就会验证失败:

找不到MX记录
Outcome: false

 

值得注意的是犹豫校验的第一步是从DNS服务器查询MX记录 所以必须联网  否则校验会失效 因为找不到MX记录会导致真实的有效地址也校验为无效 这点要特别注意。

 

此代码需要两个jar包:

1、Apache Commons Net   

maven地址:http://mvnrepository.com/artifact/commons-net/commons-net/

2、dnsjava

maven地址:http://mvnrepository.com/artifact/dnsjava/dnsjava/

 

PS:目前还没发验证部分企业邮箱,后面想办法解决这个问题之后更新此文章。

相关资源下载>>>:

dnsjava下载

Apache-commons-net下载 

参考文档:

apache-commons-net API

dnsjava API

 

3.检验电子邮件地址是否真实


同样用到了dnsjava(下载地址:http://www.dnsjava.org/)来获取MX记录。考虑到效率问题可将获取的MX记录保存到xml或数据库,下次使用时先检索本地数据,若不存在再获取其MX记录。

CheckEmail.java
import java.io.*;
 import java.net.*;
 import org.xbill.DNS.*;public class CheckEmail
 {
     public static boolean check(String mailTo)
     {
         if (!mailTo.matches("w+([-_.]w+)*@w+([-.]w+)*.w+([-.]w+)*")) { //判断格式
             return false;
         }
         String hostName = mailTo.split("@")[1];  //获得如163.com
         String host = null;  // MX记录
         
         try {
             Lookup lookup = new Lookup(hostName, Type.MX);
             lookup.run();
             if (lookup.getResult() != Lookup.SUCCESSFUL) {
                 return false;
             }
             else {
                 Record[] result = lookup.getAnswers();
                 host = result[0].getAdditionalName().toString();
             }
             
             Socket socket = new Socket(host, 25);
             BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             in.readLine();
             out.write("HELO Sender ");
             out.flush();
             in.readLine();
             out.write("MAIL FROM:<xx@xxx.xx> ");
             out.flush();
             in.readLine();
             out.write("RCPT TO:<" + mailTo + "> ");
             out.flush();
             String r = in.readLine();
             out.write("QUIT ");
             out.flush();
             
             out.close();
             in.close();
             socket.close();
             if (!r.startsWith("250")) {
                 return false;
             }
             else {
                 return true;
             }
         } catch (Exception e) {
             return false;
         }
     }
 }


 

考虑到效率&资源问题,不推荐这样验证电子邮件的真实性。
4.使用JavaMail发送邮件时如何判断收件人邮箱是否有效的解决方案


昨天公司老总提了个需求,要求给用户提供的邮箱发送邮件后,希望能收到反馈,判断是否发送成功。公司的项目是使用JavaMail来给用户发送邮件,但是调用Transport类的sendMessage方法发送邮件是没有返回值的。于是去百度,搜寻了好长时间,终于找到了两篇博客,以为能够解决这个问题,然后就去试了,可结果还是不行。

博客截图如下:

附上具体的地址:

具体的代码参考我找到的另一篇博客:

 

但是,不管我使用的是否是注册的还是未注册的邮箱,返回的结果都是: MESSAGE_DELIVERED,看了一下评论区的人,发现很多人情况和我都一看,心中充满了郁闷,因为找博客,找完整代码,整合到自己项目中来测试花了很多时间了,但是问题依旧没有解决。不过干咱们这行的,发牢骚、逃避是解决不了问题的,该干的活还是得干,于是调整了心情,我继续想办法。

我分析到:既然是要检验这个邮箱是否是有效的,那么给这个邮箱发一封邮件判断对方是否收到是一种解决方案,但却不是唯一的解决方案,我就想是不是有什么方法可以校验这个邮箱是否真实存在的呢?带着这个疑惑,我更换了在百度上用于搜索的关键字,果然,让我找到了一篇名为《Java与邮件系统交互之使用Socket验证邮箱是否存在》的博文,具体地址是:

我直接将这篇博客上的代码拷贝到我的项目上,测试了几个有效的邮箱,包括163邮箱,qq邮箱,腾讯企业邮箱,发现打印的结果都是true,然后又测试了几个不存在的邮箱,返回的结果都是false。此刻内心对这个作者充满了感激与膜拜......

 

为了表达对这段代码的喜爱,我特地复制上来跟各位一起欣赏:

package sy.util.sendemail;
  
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
  
 import org.apache.commons.lang.ArrayUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.CompareToBuilder;
 import org.xbill.DNS.Lookup;
 import org.xbill.DNS.MXRecord;
 import org.xbill.DNS.Record;
 import org.xbill.DNS.TextParseException;
 import org.xbill.DNS.Type;
  
  
 public class MailValid {
  
     public static void main(String[] args) {
 //        System.out.println(new MailValid().valid("a111aaaaaaaaaaa@163.com", "jootmir.org"));
  
         
     }
     
     /**
      * 验证邮箱是否存在
      * <br>
      * 由于要读取IO,会造成线程阻塞
      * 
      * @param toMail
      *         要验证的邮箱
      * @param domain
      *         发出验证请求的域名(是当前站点的域名,可以任意指定)
      * @return
      *         邮箱是否可达
      */
     public static boolean valid(String toMail, String domain) {
         if(StringUtils.isBlank(toMail) || StringUtils.isBlank(domain)) return false;
         if(!StringUtils.contains(toMail, '@')) return false;
         String host = toMail.substring(toMail.indexOf('@') + 1);
         if(host.equals(domain)) return false;
         Socket socket = new Socket();
         try {
             // 查找mx记录
             Record[] mxRecords = new Lookup(host, Type.MX).run();
             if(ArrayUtils.isEmpty(mxRecords)) return false;
             // 邮件服务器地址
             String mxHost = ((MXRecord)mxRecords[0]).getTarget().toString();
             if(mxRecords.length > 1) { // 优先级排序
                 List<Record> arrRecords = new ArrayList<Record>();
                 Collections.addAll(arrRecords, mxRecords);
                 Collections.sort(arrRecords, new Comparator<Record>() {
                     
                     public int compare(Record o1, Record o2) {
                         return new CompareToBuilder().append(((MXRecord)o1).getPriority(), ((MXRecord)o2).getPriority()).toComparison();
                     }
                     
                 });
                 mxHost = ((MXRecord)arrRecords.get(0)).getTarget().toString();
             }
             // 开始smtp
             socket.connect(new InetSocketAddress(mxHost, 25));
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream())));
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
             // 超时时间(毫秒)
             long timeout = 6000;
             // 睡眠时间片段(50毫秒)
             int sleepSect = 50;
             
             // 连接(服务器是否就绪)
             if(getResponseCode(timeout, sleepSect, bufferedReader) != 220) {
                 return false;
             }
             
             // 握手
             bufferedWriter.write("HELO " + domain + "\r\n");
             bufferedWriter.flush();
             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                 return false;
             }
             // 身份
             bufferedWriter.write("MAIL FROM: <check@" + domain + ">\r\n");
             bufferedWriter.flush();
             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                 return false;
             }
             // 验证
             bufferedWriter.write("RCPT TO: <" + toMail + ">\r\n");
             bufferedWriter.flush();
             if(getResponseCode(timeout, sleepSect, bufferedReader) != 250) {
                 return false;
             }
             // 断开
             bufferedWriter.write("QUIT\r\n");
             bufferedWriter.flush();
             return true;
         } catch (NumberFormatException e) {
         } catch (TextParseException e) {
         } catch (IOException e) {
         } catch (InterruptedException e) {
         } finally {
             try {
                 socket.close();
             } catch (IOException e) {
             }
         }
         return false;
     }
     
  
     private static int getResponseCode(long timeout, int sleepSect, BufferedReader bufferedReader) throws InterruptedException, NumberFormatException, IOException {
         int code = 0;
         for(long i = sleepSect; i < timeout; i += sleepSect) {
             Thread.sleep(sleepSect);
             if(bufferedReader.ready()) {
                 String outline = bufferedReader.readLine();
                 // FIXME 读完……
                 while(bufferedReader.ready())
                     /*System.out.println(*/bufferedReader.readLine()/*)*/;
                 /*System.out.println(outline);*/
                 code = Integer.parseInt(outline.substring(0, 3));
                 break;
             }
         }
         return code;
     }
 }


原博客上的jar包下载链接找不到,附上在Maven Repository中的下载地址:

http://mvnrepository.com/artifact/dnsjava/dnsjava/2.1.1

 

要是该链接失效了,可以自己到Maven 仓库官网:http://mvnrepository.com/artifact/opensymphony/quartz-all ,输入关键字:dnsjava  搜索,注意选择 dnsjava包下的资源下载,如图:

 

对找jar包有过非常痛苦的经历,所以稍微啰嗦了一下,哈哈。言归正传,

 

接着,测试了邮箱有效之后,就可以给这个邮箱发邮件了,如果正常发送,说明发送成功了,因为邮箱是有效的,至于是不是用户本人的,那就只有用户自己知道了,如果发送过程中抛了异常,那就说明发送失败。

 

但是,这其实并非真正的解决方案,真正的解决方案是:

(1)如果这个邮箱对用户和对网站自身来说很重要的话,那么在注册的时候就应该强制用户验证邮箱,这样以后发邮箱时就能保证该邮箱是有效的。

(2)对于企业邮箱,有可能用户会因为离职等原因,导致原来的邮箱不可用,或者对于那些用户主动关闭邮箱的情况,就需要调用工具类定时检验邮箱是否可用的。但是我们知道,IO是非常耗费计算机资源的,所以有必要降低IO的频率,同时避免在网站使用的高峰期进行大量的IO操作。

 

至此,问题勉强解决了。如果有更好的方法,欢迎指教。

 

解决方法一直都在,只是没有人将javamail与Socket校验这两个词关联起来,我今天做的,就是关联这两个词,仅此而已。
5.检验Email是否有效


前段时间自己做一个检验Email是否有效的工具,通过几天时间的查资料和学习,终于搞定了。主要就是登陆邮箱的smtp服务器,查找邮箱是否存在,在网上很多转载了检验Email有效性的文章,那里就是通过smtp检验的,首先说一下使用telnet登陆smtp服务器发送邮件的过程:

1、连接smtp服务器:telnet smtp.126.com 25

2、发送hello命令: helo 126.com

3、发送mail from命令,表示发件人邮箱:mail from:<test@test.com>

4、发送rcpt to命令,表示收件人邮箱,可以多次使用此命令:rcpt to:<test@126.com>

5、发送data命令,接着就是信件的内容,以“.”结束

6、发送quit命令,结束连接。

上面的每个命令都以/r/n结束,这是smtp协议。但是如今大部分邮件服务器都采用esmtp协议,防止乱发邮件,esmtp也就比smtp多了验证的步骤,在第2步与第3步之间加入验证:

发送auth login命令:auth login

接着发送用户名和密码,用户名和密码都是base64编码的,确认了用户名和密码后就可以发送mail from了。

这样我们就可以通过rcpt to返回的信息判断用户是否存在了。

 

后来我查了一下,有些在线检测邮箱有效性的网站,它们的log中显示的并不用用户名和密码登陆,也就是可以直接利用smtp协议,然后查找到它们是利用MX服务器检测的。

我们可以利用nslookup命令查找到邮箱服务器的MX服务器地址,如nslookup -type=mx 126.com

这样就会显示出126邮箱的MX服务器“126.com MX preference = 50, mail exchanger = 126.mxmail.netease.com”其中126.mxmail.netease.com就是地址了。下面是交互的过程

$ telnet 126.mxmail.netease.com 25
 Trying 220.181.15.200...
 Connected to 126.mxmail.netease.com.
 S:220 126.com Anti-spam GT for Coremail System (126com[20081010])
 C:helo localhost
 S:250 OK
 C:mail fro
 S:250 Mail OK                                         //250,表示此邮箱存在
 C:rcpt to:<
 S:550 User no            //550,用户不存在
 C:quit
 S:221 Bye
 Connection closed by foreign host.

这样如果我们要检测某个邮箱是否有效,我们只要登陆此邮箱的MX服务器,然后检测就OK了。

如果要编程实现的话,只需建立socket连接,然后发送相应的命令,检查接受到的是不是250信息就可以判断邮箱的有效性,这里要注意每次命令都要加上/r/n结束,另外还有一个重点就是要获取MX地址,这个就根据各个开发语言和工具,仁者见仁智者见智,不同的工具实现也不一样的。我用VC和C#实现过,就是运行控制台命令,然后获得命令的标准输出。
6.

如何验证一个邮箱是否正确

 https://waimaoquan.alibaba.com/bbs/read-htm-tid-2006059-fid-0.html

有时我们给一些潜在客户发了第一封邮件,邮件很快被退回来了,也就是说发送失败了,邮件发送失败的原因有很多,最可能的一种就是邮箱地址有误,根本不存在。 

如果我们发邮件失败后,我们可以去判断那个邮箱地址是不是存在的,如果不存在,我们可以把这个邮箱从我们的邮箱库中删除,这样就能提高以后的工作效率。另外,如果我们发的很多邮件被退回来,事实上我们的邮箱是会被减分的,那我们邮箱的等级也会相应降低一些,严重的可能会影响我们跟正常合作的客户的邮件沟通。 


判断一个邮箱是否存在的工具很多,这里介绍几种我经常用的,效果比较好的。 

第一个工具: 
http://verify-email.org/ 

进入这个网站,有个地方有个方框,我们把要验证的邮箱地址粘贴进去,然后点击“Verify",过一会儿,下面会显示出结果,如果是Result: Ok,说明邮箱是存在的,如果显示是Result: bad,那邮箱地址一般存在问题 

这个工具每小时好像最多只能验证5个邮箱 


第二个工具: 
http://mailtester.com/ 

使用方法很简单,这里不说了。请注意,如果系统说“Server doesn't allow e-mail address verification”,这种情况,就是无法判断的意思,这种情况邮箱一般也是没有问题的。 

第三个工具: 
http://tools.email-checker.com/ ,这个网站一般验证不出企业邮箱。 


请注意,我们去验证的时候,最好不要拿企业邮箱去验证,企业邮箱的后缀一个公司的域名后缀,如果你把这个企业邮箱放在谷歌里搜一下,能搜到一个公司的网站,那一般邮箱就没问题。另外,一般人也不会去虚构企业邮箱,因为没什么意义。

以上的验证工具我们主要是用它们来验证gmail,yahoo email,hotmail之类免费邮箱,另外,那些只是工具,准确率一般就95%左右,不是100%准确,就是一些付费的邮箱验证工具,准确率也不太可能达到100%。