群发邮件避免被识别为垃圾邮件
前言
一些企业常常要要群发邮件给客户来做推广营销,大量重复内容往往很容易被邮箱运营商判别为垃圾邮件,直接放到用户垃圾邮箱里,甚至是直接屏蔽发邮件的ip,这样就会造成群发邮件的低到达率。
如何识别垃圾邮件
那么,邮箱服务商,比如网易163,腾讯qq邮箱,怎么识别垃圾邮件呢?
一般是以下几种方法:
1、关键词识别
它首先将垃圾邮件中一些特征性的字眼收集起来(比如打折、免费、促销等),形成一个大的数据库,当一封邮件发出来的时候就会自动匹配邮件头、邮件标题、邮件内容中与这些库里的关键词特征,如果有相类似的字眼,就会判定为垃圾邮件。这种方法简单粗暴,误判的概率稍高。
2、IP黑白名单
和第一种一样,它首先会把经常发送垃圾邮件的IP收集起来,形成一个黑IP库,俗称黑名单,只要是这个黑名单中发出来的邮件,自然会被判定为垃圾邮件,相反,如果IP被划定到白名单中,那你的邮件就会畅通无阻,但前提是制作规范的邮件。
3、蜜罐技术
这是目前QQ邮箱等主流邮局采用最多的方法,它会在网络上分布很多的邮箱地址让你采集到,也会在QQ群等人流密集的地方设置一些邮箱,当你把这些邮箱采集 到自己的数据库,并且发送了邮件之后,邮局马上会发现你在发未经许可的垃圾邮件,并且将这些垃圾邮件放到数据库中,之后发送的邮件都会在这个库里进行匹 配,从而有效判定于垃圾邮件以及垃圾网址。这个判定标准也告诉我们,做邮件营销尽量不要随便从网络上采集邮址,很容易掉入蜜罐陷阱。
4、贝叶斯算法
贝叶斯算法是目前世界上用的比较多的一种算法,它首先会收集大量的垃圾邮件和非垃圾邮件,建立垃圾邮件库和非垃圾邮件库,然后提取其中的特征字符串,对大量的网络发出的邮件进行匹配和甄别,垃圾邮件的识别率非常高。
5、评分算法
这种方法是建立在关键字技术基础之上的,单一的关键字会出现大量的误判情况,为了解决这个问题,出现了多关键字检测的方式—评分。为每个可能在垃圾邮件中 出现的关键字赋予分数,分数的多少要根据关键字在垃圾邮件中出现的可能性和严重性来决定。对一封邮件进行扫描,其中有一个关键字就加一定的分数,最后将所 有的得分同设置好的阀值进行比较,从而有效判定出垃圾邮件。市场上大部分ESP都运用了此项方法。
6、DNS反向查找
在发邮件的时候,随意编造一个域名是非常容易的,如果采用阻断非法域名的方式来防止垃圾邮件的话。那么,用户可以说是被动到极点了,而且根本没有办法防 止,因为那些域名都是根本不存在的。DNS反向查找技术就是在收到邮件时对发件人的地址的真实性进行核查,防止DNS欺骗。这也是为什么正规的ESP都要 求域名进行spf和dkim设置的原因。
7、 意图分析技术
垃圾邮件技术如今变得愈加复杂,许多垃圾邮件变得与正常的邮件几乎一样,在这 些邮件中含有URL链接,这个链接往往指向一些不健康的网站,或某个商品促销的网站。ESP为此创建了意图分析技术,构建了垃圾邮件URLS地址数据库。 它检查邮件中的URL链接,确定邮件是否为垃圾邮件。
如何应对
知道了以上这些垃圾邮件识别方法,我们反其道而行之,是不是可以降低被判为垃圾邮件的概率呢?我们可以做以下尝试:
- 降低同一个邮箱地址发送邮件的频率,使用多个邮箱账号发送邮件;
- 降低同一个ip地址发送邮件的频率,使用多个ip发送邮件;
- 邮件中减少易被判为垃圾邮件的相关词汇;
- 邮件内容页面美观,尽量避免用户反感,减少用户投诉;
- 增加邮件退订功能,一旦收件人点了退订,就不要再次给人家发送,不要像那些垃圾短信一样搞个假退订,糊弄谁呢,只能让人更反感;
下面,使用Java代码来实现邮件群发,我的想法是:
- 大量的邮件不要在很短时间内全部发送完,应该有一定的时间间隔;
- 通过设置阈值(邮件运营商能接受的频率,比如同一个发件人一天只能给136邮箱发送一百封邮件)来控制邮件发送的频率,一个邮箱账号每天发送到同一个ESP的邮件数不超过阈值;
- 当天发送不完的邮件,往后延续一天发送,如何延迟发送呢?把邮件放到ActiveMQ里,利用mq的延时投递功能;
- 邮件发送时间尽量在白天,比如当天早上八点,到晚上八点,不要在八点以后骚扰人家;
Java实现
MQ的配置如下,把邮件消息放到一个叫BATCH_EMAIL的消息队列里,然后监听这个消息队列,收到消息就发送邮件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="queueConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://127.0.0.1:61616" /><property name="userName" value="${mqUserName}" /><property name="password" value="${mqPassword}" /><property name="useAsyncSend" value="true" /> </bean><bean id="emailDestination" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="BATCH_EMAIL" /> </bean> <bean id="simpleMessageConverter" class="org.springframework.jms.support.converter.SimpleMessageConverter" /><bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"><property name="connectionFactory" ref="queueConnectionFactory" /><property name="defaultDestination" ref="emailDestination" /><property name="messageConverter" ref="simpleMessageConverter"/><property name="pubSubDomain" value="false" /> </bean><bean id="emailMessageReceiver" class="cn.mns.mq.EmailMessageReceiver" /><bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="queueConnectionFactory" /> <property name="destination" ref="emailDestination" /> <property name="messageListener" ref="emailMessageReceiver" /> </bean> </beans>
制定邮件发送计划,将群发的邮件信息(邮件内容,地址,主题等)放到消息队列里:
package cn.mns.tools.email;import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;import cn.mns.common.SpringContextInit;
import cn.mns.mq.EmailProductor;
import cn.mns.tools.DateUtil;
import cn.mns.tools.StringUtil;/*** 邮件群发计划* @author bukale* @date 2019-12-18*/
public class BatchEmailPlan {//每个账号单个邮件服务商每天发送邮件数阈值private static int LIMIT = 100;//账号数,自己用来发送邮件的账号数private static int ACCOUNT_COUNT = 80;//第几天计数器,用于计算发送邮件时间private static int DAY_COUNTER = 0;private static List<String> accountList;static{//初始化账号list,我的账号是有规律的,可以改成你自己的实现,放到accountList即可accountList = new ArrayList<String>();for(int i = 1;i <= ACCOUNT_COUNT;i ++){accountList.add("post" + i + "@test.com");}}/*** 制定群发计划* 将邮件分配给不同的账号并发送至mq* @param emailList 邮件列表* @param subject 邮件主题* @param templateFile 模板文件路径* @param siteType 网站类型*/public static void makeBatchPlan(List<String> emailList,String subject,String templateFile,String siteType){System.out.println("=======================" + emailList.size() + "======================");EmailProductor emailProductor = getEmailProductor();// 一个账号可以发的邮件数=域名数*阈值*天数 5*100*1=500 Map<String,Integer> counter = new HashMap<String, Integer>();//域名和对应邮件数,用于统计Map<String,List<String>> domainEmails = new HashMap<String, List<String>>();//域名和对应邮件地址listList<String> nextList = new ArrayList<String>();//每天超过上限的邮件,存放起来,隔天发送String email = "";String domain = "";Integer count = 0;//遍历,对所有邮件地址进行按域名统计和分类for(int i = 0;i < emailList.size();i ++){email = emailList.get(i);if(!StringUtil.isEmail(email)){continue;}domain = email.replaceAll("\\S+@","");count = counter.get(domain);counter.put(domain,count == null ? 1 : count + 1);if(domainEmails.containsKey(domain)){domainEmails.get(domain).add(email);}else {List<String> list = new ArrayList<String>();list.add(email);domainEmails.put(domain,list);}}int maxCount = 0;for(String key : counter.keySet()){int value = counter.get(key);System.out.println("域名:" + key + "\t邮件数:" + value);if(maxCount < value){maxCount = value;}}System.out.println("最大邮件数:" + maxCount + "\t阈值:" + LIMIT);System.out.println("预计天数:" + (int)Math.ceil(maxCount*1.0 / (LIMIT * ACCOUNT_COUNT)));long time1 = getStartTime();long time2 = getEndTime();Iterator<Entry<String, List<String>>> ite = domainEmails.entrySet().iterator();Entry<String, List<String>> entry = null;String key = "";List<String> eList = null;while(ite.hasNext()){entry = ite.next();key = entry.getKey();eList = domainEmails.get(key);int size = eList.size();//所有账号可以发送的邮件总数int limit = LIMIT * ACCOUNT_COUNT;//找出超出发送总量的邮件,用于下次执行if(size > limit){List<String> subList = eList.subList(limit,size);nextList.addAll(subList);}String userEmail = "";//每次分配的起点终点表示区间为//[total*taskNumber/cores, total*(taskNumber+1)/cores)int total = size > limit ? limit : size;//总邮件数for(int taskNumber = 0; taskNumber < ACCOUNT_COUNT; taskNumber++){int max = total * (taskNumber + 1) / ACCOUNT_COUNT;int j = total * taskNumber / ACCOUNT_COUNT;// System.out.println();// System.out.print(taskNumber + "-" + accountList.get(taskNumber) + ":");for (int i = j; i < max; i++) {userEmail = eList.get(i);// System.out.print(userEmail + ",");Map<String,String> map = new HashMap<String, String>();map.put("email",userEmail);//收件人邮箱地址map.put("subject", subject);//邮件主题map.put("account",accountList.get(taskNumber));map.put("templateFile",templateFile);//邮件内容模板,即邮件内容//邮件信息发送到mqemailProductor.sendEmailMessage(map,getDelay(time1,time2,Math.abs(i-max)));//emailProductor.sendEmailMessage(map,300000);}}}System.out.println();System.out.println("nextList:" + nextList.size());//没法送完的,继续下一次发送if(nextList.size() > 0){DAY_COUNTER ++;makeBatchPlan(nextList,subject,templateFile,siteType);}else {DAY_COUNTER = 0;}}public static EmailProductor getEmailProductor(){return SpringContextInit.getApplicationContext().getBean(EmailProductor.class);}/*** 获取起始时间,当天早上八点,或者第二天早上八点* @return* @date 2019-12-23*/public static long getStartTime(){DateUtil du = new DateUtil();Date startDate = du.DATEADD("d",DAY_COUNTER,new Date());String timeStr = du.FormatDate(startDate,"yyyy-MM-dd HH");int hour = Integer.valueOf(timeStr.substring(11));String dateStr = timeStr.substring(0,10);String str = "";if(hour < 8 || DAY_COUNTER > 0){str = dateStr + " 08:00:00";}else if(hour >= 20){str = du.DATEADD("d",1,dateStr) + " 08:00:00";}else {str = du.FormatDate(startDate,"yyyy-MM-dd HH:mm:ss");}startDate = DateUtil.getFormatDate(str,"yyyy-MM-dd HH:mm:ss");System.out.println("start:" + du.FormatDate(startDate,"yyyy-MM-dd HH:mm:ss"));return startDate.getTime();}/*** 获取截止时间,当天晚上八点,或者第二天晚上八点* @return* @date 2019-12-23*/public static long getEndTime(){DateUtil du = new DateUtil();Date endDate = du.DATEADD("d",DAY_COUNTER,new Date());String dateStr = du.FormatDate(endDate,"yyyy-MM-dd") + " 20:00:00";endDate = DateUtil.getFormatDate(dateStr,"yyyy-MM-dd HH:mm:ss");System.out.println("end:" + du.FormatDate(endDate,"yyyy-MM-dd HH:mm:ss"));return endDate.getTime();}/*** 获取延时时间* @param delay* @param i* @return* @date 2019-12-23*/public static long getDelay(long startTime,long endTime,int i){long delay = (endTime - startTime) / LIMIT;//每封邮件时间间隔long basic = Math.abs(startTime - System.currentTimeMillis());return basic + delay * i + new Random().nextInt(600000);//标准时间+10分钟的随机}public static void main(String[] args) {List<String> emailList = new ArrayList<String>();for(int i = 0;i < 10000;i ++){emailList.add("e" + new Random().nextInt(100) + "@qq.com");}for(int i = 0;i < 3000;i ++){emailList.add("e" + new Random().nextInt(100) + "@136.cn");}for(int i = 0;i < 2000;i ++){emailList.add("e" + new Random().nextInt(100) + "@gmail.com");}for(int i = 0;i < 5000;i ++){emailList.add("e" + new Random().nextInt(100) + "@163.com");}String template = "";String subject = "";BatchEmailPlan.makeBatchPlan(emailList,subject,template,"");}}
先注释掉邮件发送到mq的方法调用,运行main方法,可以看到,邮件的发送计划已经制定好了:
package cn.mns.mq;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;/*** 邮件消息发送* @author bukale* @date 2019-12-20*/
@Component
public class EmailProductor {@Autowiredprivate JmsTemplate jmsTemplate;public void sendEmailMessage(Map<String,String> map,long delay){jmsTemplate.convertAndSend(map,new DelayMessagePostProcessor(delay));}}
接收队列里的消息并发送邮件:
package cn.mns.mq;import java.util.HashMap;
import java.util.Map;import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;import cn.mns.constant.SiteEnum;
import cn.mns.tools.email.EmailHelper;
import cn.mns.tools.email.EmailHelperFactory;
import cn.mns.tools.email.EmailSendException;/*** 接收MQ消息队列中的邮件消息并发送邮件**/
public class EmailMessageReceiver implements MessageListener {public void onMessage(Message msg) {MapMessage mapMsg = (MapMessage)msg;try {String email = mapMsg.getString("email");//要发送的邮箱地址String account = mapMsg.getString("account");//发送该邮件的邮箱账号String subject = mapMsg.getString("subject");//邮件主题String templateFile = mapMsg.getString("templateFile");//邮件内容// 这个类是一个自己项目的类,就是实现了发送邮件的功能EmailHelper emailHelper = EmailHelperFactory.getEmailHelper();emailHelper.setSubject(subject);emailHelper.setTemplateFile(templateFile);Map<String,Object> params = new HashMap<String,Object>();params.put("subject",subject);params.put("email",email);try {// 发送邮件emailHelper.sendEmail(email,account,params);} catch (EmailSendException e) {e.printStackTrace();}} catch (JMSException e) {e.printStackTrace();}}}
日期工具类:
package cn.mns.tools;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;public class DateUtil {public static String getFormatDate(Date date,String format) {if(date == null){return null;}return new SimpleDateFormat(format).format(date);}public static Date getFormatDate (String strDate, String format) {Date date = null;if (strDate != null && !"".equals(strDate)) {if (format == null || "".equals(format)) {format = "yyyy-MM-dd";}SimpleDateFormat sdf = new SimpleDateFormat(format);try {date = sdf.parse(strDate);} catch (ParseException e) {e.printStackTrace();date = new Date();}}return date;}//输入Date得到所需格式的日期字符public String FormatDate(Date date, String formatType) {java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat(formatType);String strDate = formatter.format(date);return strDate;}//得到特定日期加上给定时间后的日期public GregorianCalendar DATEADD(String datepart, int number,GregorianCalendar inputdate) {int normalDatepart = 0;datepart = datepart.toLowerCase();if (datepart.equals("y") || datepart.equals("yy")|| datepart.equals("yyy") || datepart.equals("yyyy"))normalDatepart = inputdate.YEAR;if (datepart.equals("m") || datepart.equals("mm"))normalDatepart = inputdate.MONTH;if (datepart.equals("d") || datepart.equals("dd"))normalDatepart = inputdate.DATE;if (datepart.equals("wk") || datepart.equals("ww")) {normalDatepart = inputdate.DATE;number = number * 7;}if (datepart.equals("hh") || datepart.equals("h"))normalDatepart = inputdate.HOUR;if (datepart.equals("mi"))normalDatepart = inputdate.MINUTE;if (datepart.equals("s") || datepart.equals("ss"))normalDatepart = inputdate.SECOND;if (datepart.equals("ms"))normalDatepart = inputdate.MILLISECOND;GregorianCalendar tempDate = new GregorianCalendar();tempDate.setTime(inputdate.getTime());tempDate.add(normalDatepart, number);return tempDate;} //输入字符串public String DATEADD(String datepart, int number, String strInputDate) {java.util.GregorianCalendar gcResult = new GregorianCalendar();String outDate = "";String formatType = "yyyy-MM-dd";String tempInputDate = "";if (strInputDate.indexOf(".") >= 0)formatType = "yyyy.MM.dd";tempInputDate = strInputDate;if (strInputDate.indexOf(".") >= 0)formatType = "yyyy.MM.dd";if (tempInputDate.indexOf(":") >= 0) //判断是哪种格式.formatType = formatType + " hh:mm";tempInputDate = tempInputDate.substring(tempInputDate.indexOf(":") + 1);if (tempInputDate.indexOf(":") >= 0)formatType = formatType + ":ss";java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat(formatType);java.text.ParsePosition pos = new java.text.ParsePosition(0);java.util.Date sourceDate = formatter.parse(strInputDate, pos);GregorianCalendar gc = new GregorianCalendar();gc.setTime(sourceDate);gcResult = DATEADD(datepart, number, gc);String strDate = formatter.format(gcResult.getTime());outDate = strDate;if (formatType.equals("yyyy-MM-dd hh:mm")|| formatType.equals("yyyy-MM-dd hh:mm:ss")|| formatType.equals("yyyy.MM.dd hh:mm")|| formatType.equals("yyyy.MM.dd hh:mm:ss")) //如果是下午,则要加12个小时。{if (gcResult.get(Calendar.AM_PM) == 1) //AM_PM的值 1代表下午 0 代表上午{String strPMHour = Integer.toString(Integer.parseInt(strDate.substring(11, 13)) + 12);outDate = strDate.substring(0, 11) + strPMHour+ strDate.substring(13);}}return outDate;}//输入Datepublic Date DATEADD(String datepart, int number, Date dateInputDate) {java.util.GregorianCalendar gc = new GregorianCalendar();java.util.GregorianCalendar gcResult = new GregorianCalendar();gc.setTime(dateInputDate);gcResult = DATEADD(datepart, number, gc);return gcResult.getTime();}public static void main(String[] args){System.out.println(getFormatDate("2015-08", "yyyy-MM"));}}
群发邮件避免被识别为垃圾邮件相关推荐
- 邮件因时间问题被识别为垃圾邮件 [3.4 FH_DATE_PAST_20XX The date is grossly in the future.]
最近很多邮件被识别为垃圾邮件,总结了一下,这些邮件都因为一项评分高导致的,具体如下: 3.4 FH_DATE_PAST_20XX The date is grossly in the fut ...
- 电影推荐系统、数字识别、垃圾邮件参考文献
电影推荐(吴恩达,唐宇迪) 外文文档 https://www.ethanrosenthal.com/2015/11/02/intro-to-collaborative-filtering/ G站 ht ...
- Python+sklearn使用朴素贝叶斯算法识别中文垃圾邮件
总体思路与步骤: 1.从电子邮箱中收集垃圾和非垃圾邮件训练集. 2.读取全部训练集,删除其中的干扰字符,例如[]*..,等等,然后分词,删除长度为1的单个字. 3.统计全部训练集中词语的出现次数,截取 ...
- 【独立站运营】营销邮件被判定为垃圾邮件?四个方法教你避开
电子邮件是当前流行的信息通讯方式,具有低廉.快速的特性.相较于国人依赖使用微信等即时通讯软件,少有查看邮件箱的情况,外国人一般都拥有定时查看邮件的习惯.基于此,独立站卖家经常通过群发邮件的方式,向买家 ...
- Python微调文本顺序对抗朴素贝叶斯算法垃圾邮件分类机制
封面图片:<Python可以这样学>,ISBN:9787302456469,董付国,清华大学出版社 图书详情(京东): ================= 关于朴素贝叶斯算法中文垃圾邮件分 ...
- 伪造邮件***,看我如何给网易邮箱APP发送垃圾邮件【二】
Duang~~~好久没更新博客了,源于最近比较忙,感谢博友对我的支持哈~今天继续更新 后面以我在补天漏洞平台提交过的漏洞为入口. 网易邮箱国内用户很多.今天看见他家的app了 ,99.95%垃圾邮件捕 ...
- 与PHP对抗招聘者垃圾邮件-概念证明
Ever since I moved off of Google services (due to quality, not privacy concerns), I'd been looking f ...
- 利用朴素贝叶斯模型识别垃圾邮件
文章出处: http://blog.csdn.net/gane_cheng/article/details/53219332 http://www.ganecheng.tech/blog/532193 ...
- WP插件教程—AKISMET反垃圾邮件插件
Akismet 插件提供WordPress垃圾评论过滤服务,并使用其算法过滤掉垃圾邮件.若多个网站将内容相似的邮件报告为垃圾邮件,Akismet以后都会把此类内容识别为垃圾邮件. Akismet的主要 ...
- 反垃圾邮件产品等级划分
声明 本文是学习信息安全技术 反垃圾邮件产品技术要求和测试评价方法. 下载地址 http://github5.com/view/1442而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系 ...
最新文章
- 《JAVA与模式》之备忘录模式
- BI之SSAS完整实战教程3 -- 创建第一个多维数据集
- shell监控MySQL服务是否正常
- DB2数据库报 [SQL0805N Package NULLID.SQLLD003 was not found.]
- java sscanf_sscanf 详细说明
- HDR 成像技术学习(二)
- 如何在 .Net Core 中使用 IHostedService
- ATcoder-[AGC048B]Bracket Score【结论,贪心】
- python调用vbs脚本_xShell终端调用VBS脚本 使用方法说明
- Excel筛选后填充
- Ubuntu9.10下永中Office2009安装
- 好汉歌计算机音乐,好汉歌歌曲赏析
- jQuery 读书笔记之一
- HbuilderX 快捷键修改
- 劈开迷雾:蘑菇街搜索架构及搜索排序实践
- VPP线程之间报文调度
- 清除maven仓库lastUpdated文件
- List和IList的区别
- matlab对于椭圆检测的算法,基于弧段组合的椭圆检测算法研究
- 一个计算机入门者的故事
热门文章
- RS485以及MODBUS知识积累
- “教你如何刷Q币” 不要上当受骗
- java反射获取一个对象中属性(field)的值
- Docker服务,堆栈和分布式应用程序捆绑
- 前后端利用accessToken与refreshToken无感刷新
- Netflix Web 性能案例研究
- Apache DolphinScheduler 3.0.0 正式版发布!
- android solid代码,Android中形状图形 | shape图形常用的3个节点:corners(圆角)、solid(填充) 和 stroke(描边)...
- 1999-2019,互联网失落者
- Cisco Packet Tracer安装