前几天公司一个产品要搞活动,共收集了近1.2W个会员的QQ邮箱,打算来一次群发,这算是企业中最常见的需求了,本文记录了此次服务器的搭建和群发过程,没有什么技术含量,又充满了投机取巧的做法,只供参考,大牛不要笑话。

一、准备

QQ邮箱作为国内用户量第一的邮箱,反垃圾做的还是非常不错的。既然是邮件推广,势必要短期内发送大量内容相同的邮件,为了不被QQ邮箱识别为垃圾邮件,我主要采用了多ip轮询和控制发送频率两种方法,其它设置按照正规企业邮箱的方式来搭建,并没有什么黑科技。
服务器选择:hostwinds 购买额外的ip,共11个
原则是:

  1. ip多,便于轮询发送
  2. 网络稳定,带宽不要太小,避免发送时发生阻塞
  3. ip信誉好,查询ip的信誉: http://www.dnsbl.info/

系统:centos 6.2 64位 Openvz
软件:postfix + mutt
域名:一个干净的域名

二、环境搭建

群发邮件只需发送即可,不用接收邮件,采用mutt配合脚本发送,也不用配置imap和pop3,所以非常省事,只要配置好postfix即可。

1.安装postfix和mutt

yum install postfix mutt 

2.配置postfix

备份默认配置:


cp /etc/postfix/main.cf /etc/postfix/main.cf.bak

main.cf需要修改的地方很少,直接贴配置:

queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
myhostname = mail.nixops.me //hostname改成你自己的域名
mydomain = nixops.me        //改成自己的域名
myorigin = $mydomain
inet_interfaces = all //服务器有多个ip,随便使用一个ip,也可以指定成all
inet_protocols = ipv4       //指定使用ipv4,关闭ipv6,提高发送速度,否则会先尝试ipv6
mydestination = $myhostname, localhost.$mydomain, localhost
unknown_local_recipient_reject_code = 550
relay_domains = $mydestination
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

debug_peer_level = 2
debugger_command =
     PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
     ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop
html_directory = no
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/postfix-2.6.6/samples
readme_directory = /usr/share/doc/postfix-2.6.6/README_FILES

重启postfix:

service postfix restart

测试一下:

 cat /etc/issue |mutt -e 'my_hdr from:sender@nixops.me' -s test_email_subject  willis@nixops.me
 

3.配置多ip轮询发送

postfix不带多ip轮询的功能,虽然可以指定inet_interfaces来切换发送ip,但是没有同时使用多ip进行发送。通常来说,postfix多ip轮询有两种方法:1.配置多个smtp,每个绑定到不同ip 2.通过iptables的postrouting链来替换25端口的出口ip。
使用第一种方式要通过程序调用多个smtp来实现ip轮询,效率不高,还要多写不少代码。我这里为了偷懒就使用iptables来完成:

iptables -A POSTROUTING -o venet0 -p tcp -m state --state NEW -m tcp --dport 25 -m statistic --mode nth --every 11 -j SNAT --to-source 1.1.1.1 
iptables -A POSTROUTING -o venet0 -p tcp -m state --state NEW -m tcp --dport 25 -m statistic --mode nth --every 11 -j SNAT --to-source 2.2.2.2

服务器有多少ip就写多少条规则,如果不是11个ip,--every 11也要改,把--to-source改成服务器的ip。如果你的网卡不是venet0,也要改成相应的。

测试:使用上面的命令多发送几封邮件,然后检查邮件头中的发送ip是否有随机使用服务器的多个ip。如果是发送到web邮箱,可能查看不了邮件头,可以用邮件客户端看邮件头,例如outlook。

4.域名配置

域名主要是配置mx记录和spf记录,如果效果还不满意可以配置DKIM,建议MX记录设置多条。
以nixops.me为例:

nixops.me

  • mx1 ---> mail1.nixops.me //第一个mx记录
  • mx2 ---> mail2.nixops.me //第二个mx记录,优先级设置不同
  • spf ---> v=spf1 include:spf.mail.nixops.me -all //txt记录,配置spf

mail1.nixops.me/mail2.nixops.me/spf.mail.nixops.me
这三个域名设置多个A记录,把服务器的所有ip都加进去。

当然这样配置十分简陋,还有好多地方要调整,不过对于发广告邮件足够了。如果发送要求较高或者是正式邮件服务器,可以使用 www.mail-test.com检查下具体问题,在有针对性处理。

三、邮件群发脚本

通过上面配置后,发送脚本就可以写的非常简单了,因为不用调用smtp,使用python直接调用系统命令mutt发送邮件,每发送一封邮件后等待5秒钟在发送下一封。

#! /usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'willis@nixops.me'

import sys,os,time,datetime

timeNow = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

list_file = open('list.txt')
try:
    temp_addr = list_file.readlines()
finally:
    list_file.close()
addr_list = temp_addr

conf_file = open('set.txt')
try:
    temp_conf = conf_file.readlines()
finally:
    conf_file.close()
set_list = temp_conf

def get_mail_fomat(index):
    return_str = set_list[index]
    return_str = return_str.replace("\n","")
    return_str = return_str.split('=')[1]
    return return_str

def send(to_addr,from_addr,subject,content,nick):
    try:
    os.system("cat %s|mutt -e 'set content_type=text/html' -e 'set realname=%s'  -e 'my_hdr from: %s'  -s %s  %s " % (content,nick, from_addr, subject, to_addr))
    os.system("echo '%s send to --> %s' >>log.txt " % (timeNow, to_addr))
    time.sleep(5)
        return True
    except:
        return False

if __name__ == '__main__':
    send_succeed_num = 0
    send_fail_num = 0
    mail_from = get_mail_fomat(0)
    mail_nick = get_mail_fomat(1)
    mail_subject = get_mail_fomat(2)
    mail_content = get_mail_fomat(3)

    for i in addr_list:
        if send(i,mail_from,mail_subject,mail_content,mail_nick):
            send_succeed_num +=1
        else:
            send_fail_num +=1
    os.system("echo 'All mails send finished! total Send --> %s' >>log.txt " % send_succeed_num)
    os.system('''echo "邮件群发已经完成,共发送%s封邮件。如有其它需要,请联系IT或运维" |mutt -s '请注意,邮件群发已经完成!' -e 'my_hdr from: sa@nixops.me' -c 'willis@nixops.me' ''' % send_succeed_num)

我要发送的邮件内容是html,将内容写入到文件email.html,群发的邮箱每行一个写入到list.txt,发送的配置写入set.txt文件。set.txt内容:

sender_addr='sender@xxx.com'   
sender_nick='no_reply'
mail_subject='XX集团XX活动'
mail_content='email.html'

mutt是从系统读取语言设置,如果邮件内容或标题中有乱码,需将系统语言设置文中文:

cat /etc/sysconfig/i18n
LANG="zh_CN.GB2312"

5秒钟使用随机ip发送一封邮件,切到后台发了一天才全完成,分析了一下postfix日志:

开始发送时间        : Mar 25 12:43:00
发送结束时间         :Mar 26 04:37:19
邮箱地址总数         : 11364
邮箱地址不存在        : 343
有效邮箱数           :11021
邮箱地址准确率       :96.98 %
被标识为垃圾邮件     : 321
发送成功数           : 10700
发送成功率           :97.08 %

当然这个是理想状态,实际成功率会低于97.08%,不过从反馈来看效果还是很不错的。

参考文章:
http://www.blackhatworld.com/blackhat-seo/making-money/488164-tutorial-how-set-up-your-own-linux-smtp-server-ip-rotation-rdns-spf-dkim.html
http://www.linuxidc.com/Linux/2015-03/115484p3.htm