使用postfix和mutt群发邮件
前几天公司一个产品要搞活动,共收集了近1.2W个会员的QQ邮箱,打算来一次群发,这算是企业中最常见的需求了,本文记录了此次服务器的搭建和群发过程,没有什么技术含量,又充满了投机取巧的做法,只供参考,大牛不要笑话。
一、准备
QQ邮箱作为国内用户量第一的邮箱,反垃圾做的还是非常不错的。既然是邮件推广,势必要短期内发送大量内容相同的邮件,为了不被QQ邮箱识别为垃圾邮件,我主要采用了多ip轮询和控制发送频率两种方法,其它设置按照正规企业邮箱的方式来搭建,并没有什么黑科技。
服务器选择:hostwinds 购买额外的ip,共11个
原则是:
- ip多,便于轮询发送
- 网络稳定,带宽不要太小,避免发送时发生阻塞
- 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
你好,看了你的文章很受启发,感谢~
有一个问题请教一下,如果postfix是多实例的,比如还有一个实例名叫postfix-2,那么如何使用mutt通过postfix-2这个实例发送邮件呢?谢谢~
不知道为什么。我使用iptables的时候总是提示命令:
iptables v1.4.7: Invalid port:port syntax - use dash
Try `iptables -h' or 'iptables --help' for more information.
博主,这种方式发送的时候spf记录不通过,请问这个情况怎么解决 啊?
如果我是单网卡eth0, 并绑定了几个ip的话:eth0:1 eth0:2 eth0:3
这样的话,你的配置中venet0 改为eth0就可以了吧?
我可以不用客户端来控制发送间隔,直接更改every 后面数字起到发送频率的作用就可以了.
请问是否你那个没发送完一个邮件停止5秒怎么做到的? 因为我们的邮件包含了远端调用网站页面,所以我想控制发送时间,我搭建了postfix和dovocat来处理回弹.
在用客户端情况下如何做到控制发送邮件间隔时间的?
用客户端控制发送间隔,客户端没这个功能的话,建议还是用脚本吧