当前位置: 首页>編程日記>正文

Python smtp拟人个性化群发邮件,imap退信批量处理和SuiteCRM结合使用问题

Python smtp拟人个性化群发邮件,imap退信批量处理和SuiteCRM结合使用问题

虽然现在邮件用得越来越少,但跟即时通讯相比,邮件还是有它一些独有的特质,因此某些事务中还是会用到它。
SuiteCRM有群发邮件功能,但因为目前国家相关部门的规定限制,通常一个邮箱每天只能发1000个邮件,而且有一定的节奏要求,所以无法使用SuiteCRM来群发邮件,为了完成相应的业务,必须有另外招数。
通常的分享只考虑发或收,在此两个方向都考虑到了,而且结合CRM就可以把相关信息更有针对性地保留下来,并且分配给相关人员。

在此分享一个群发2万份邮件具体思路
Python是眼下最简单有效解决小问题的编程语言,所以选择Python。在网上可以找到很多Python收发邮件的例子,也不需要很长代码。
整体思路:
这是一个从成本角度无法全自动的项目,只能分段进行自动化。

  1. 从SuiteCRM导出相关的邮箱地址,进行删选,然后生成Python可读地址文件;
  2. 用Python写一个推送程序,大概200行足矣,账号、密码、发送节奏,循环次数都存放在一个设置文件里,以便随时修改,发送日志;
  3. 用Python回信处理程序,账号、密码存放在一个设置文件里,以便随时修改,收邮件清单,以便批量处理,特别针对无效邮箱。

具体做法:
在此同各位分享一下具体步骤中的一些细节

  1. 因为在SuiteCRM的界面上总是有些绊手绊脚的,所以这里笔者直接进入数据库,下面是用SuiteCRM原始潜在客户leads表格写的一句SQL命令,包括的数据项有:邮箱地址、人名加称谓、数据唯一标识码(简称ID)、 数据最后修改日期、所在城市,按修改日期排序;这是一个最基本选择,实际应用场景可以非常多的细分,在此就此略过;在这基础上把数据导入Excel就可以交给任何一个熟练白领根据具体情况进行对数据分组等一系列群发邮件前的准备工作,可以任意发挥,技术上没有什么难度,几乎想怎么样就可以怎么样,效率也是很高的。
SELECT c.email_address, CONCAT(a.last_name, if(a.salutation='Mr.','先生',if(a.salutation='Ms.','女士','先生/女士'))) as name, a.id, a.date_modified, a.primary_address_city FROM leads a INNER JOIN email_addr_bean_rel b ON a.id = b.bean_id INNER JOIN email_addresses c ON b.email_address_id = c.id WHERE a.deleted=0 AND b.deleted=0 AND LENGTH(a.last_name) <10 AND c.invalid_email = 0 ORDER BY a.date_modified
  1. 在上面的邮箱、人名及分组准备好以后,就可以进行布置群发了;群发需要有几样东西:
    a. 要有一个有邮箱,包括邮箱imap地址,用户名,密码
    b. 邮件内容包括个性化变量,一般为html格式
    c. 收件人邮箱地址和人名清单,在此还有ID,以便把最终结果导入CRM
    d. 要有推送程序,在此是在网上参考了许多帖子后自己写的Python 3程序,程序本身包括python源码(mailsender.py)、控制部分(config.txt)、邮件内容(message.txt)、邮箱人名清单(addresses.txt) 四部分,推送程序还会写一个发送成功清单(logs.txt)和一个发送失败清单(failed.txt)用于核对和检查
    e. 推送的节奏是有讲究的,太快了会被邮箱服务商挡住,太慢了会来不及,这个需要花时间摸索,一般销售的信息也不是很准,网易企业邮箱每天每个账号只能发一千个邮件(这样相对也出不了大事,避免被封),间隔最好大于10s,其他的说法都不可靠;有的销售会给你一个比较明确的信息,而有的却吾哩嘛里如阿里云的一个销售不给相关信息,来来回回浪费了好几天,具体需要自己花时间挖掘。
# Python 3
import time
import datetime
import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMETextsleeptime = 1
pausetime = 0
numMails = 10
numRounds = 1
SMTPUsername = ''
SMTPPassword = ''
SMTPServer = 'smtp.gmail.com'
SMTPPort = 587
sender = ''
confirmationEmail = ''j = 0
count = 0
failed = 0def configuration(resume=""):global sleeptimeglobal pausetimeglobal numMailsglobal numRoundsglobal SMTPUsernameglobal SMTPPasswordglobal SMTPServerglobal SMTPPortglobal senderglobal jglobal confirmationEmail# resume from last sent mail according to logfileif resume == "" and os.path.isfile("logs.txt"):resume = input("Resume from last position?(Y/N)\n")elif resume == "":resume = "No"print("resuming from logs: ", resume)if resume[0].upper() == "Y":log = open("logs.txt", encoding='utf-8').read().split("\n")j = int(log[len(log) - 2].split("\t")[0]) + 1# load configuration from filewith open("config.txt") as conf:config = conf.read()config = config.split("\n")for line in config:if len(line) == 0:continueif line[0] == "#":continueoption = line.split(" = ")if option[0] == "sleeptime":sleeptime = float(option[1])elif option[0] == "pausetime":pausetime = float(option[1]) * 60elif option[0] == "numMails":numMails = int(option[1])elif option[0] == "numRounds":numRounds = int(option[1])elif option[0] == "SMTPUsername":SMTPUsername = option[1]elif option[0] == "SMTPPassword":SMTPPassword = option[1]elif option[0] == "SMTPServer":SMTPServer = option[1]elif option[0] == "sender":sender = option[1]elif option[0] == "SMTPPort":SMTPPort = int(option[1])elif option[0] == "confirmationEmail":confirmationEmail = option[1]else:print("invalid option: ")print(option)if sender == "":sender = SMTPUsernameif confirmationEmail == "":confirmationEmail = senderprint("configuration finished")def send():global sleeptimeglobal pausetimeglobal numMailsglobal numRoundsglobal SMTPUsernameglobal SMTPPasswordglobal SMTPServerglobal SMTPPortglobal senderglobal jglobal countglobal failed# connect to mailserver# server = smtplib.SMTP(SMTPServer, SMTPPort)# server.starttls()addresses = (open("addresses.txt", encoding='utf-8').read() + "\n").split("\n")# if not specified, send all mailsif numMails == 0:numMails = int((len(addresses) // numRounds) + 1)for k in range(numRounds):# connect to mailserverserver = smtplib.SMTP(SMTPServer, SMTPPort)server.starttls()# logintry:server.login(SMTPUsername, SMTPPassword)except Exception as e:print("login failed")print(e)# assemble individual messagesfor i in range(numMails):sent = False# find next email recieverwhile len(addresses[k * numMails + i + j]) <= 1:print("skip empty line")j += 1if k * numMails + i + j >= len(addresses):breakindex = k * numMails + i + jif index >= len(addresses):print("end reached")breakreciever = addresses[index].split(";")msg = MIMEMultipart('alternative')msg['From'] = sendermsg['To'] = reciever[0]html = ""try:with open("message.txt", encoding='utf-8')as f:subject = f.readline()html = f.read()except Exception as e:print("could not read message")print(e)msg['Subject'] = subjectif len(html) < 1:print("message could be empty")html = html.replace('placeholder', reciever[1])part2 = MIMEText(html, 'html')msg.attach(part2)try:server.send_message(msg)count += 1sent = Trueexcept Exception as e:print("message could not be sent")print(e)print("messages sent:", count)# write logswith open("logs" + ".txt", "a+", encoding='utf-8') as log:log.write(str(index) + "\t" + reciever[0] + "\t" + reciever[1] + "\t" + reciever[2] + "\t" + "sent: " + str(sent) + "\t" + str(datetime.datetime.now()) + "\n")if not sent:failed += 1with open("failed" + ".txt", "a+", encoding='utf-8') as fail:fail.write(reciever[0] + ";" + reciever[1] + ";" + reciever[2] + "\n")print("sleeping", sleeptime, "s")time.sleep(sleeptime)if index + 1 >= len(addresses):print("end reached")breakprint("paused", pausetime, "s")time.sleep(pausetime)def confirm():server = smtplib.SMTP(SMTPServer, SMTPPort)server.starttls()server.login(SMTPUsername, SMTPPassword)msg = MIMEMultipart()msg['From'] = sendermsg['To'] = confirmationEmailmsg['Subject'] = "Emails sent"message = "successfully sent " + str(count) + " messages\n" + str(failed) + " messages could not be sent\n" + str(datetime.datetime.now())msg.attach(MIMEText(message))server.send_message(msg)configuration()
send()
confirm()

这个推送程序的亮点是操作简单,邮箱、节奏、每次群发多少邮件个数,每次群发间的停顿时间,每天群发次数,都在config.txt里定义,可以根据邮件服务器的反馈马上调整,极其方便;可以在一般windows里运行,也可以到服务器上运行;不同账号可以用不同文件夹来同时群发邮件,以便节省时间;有两种日志方便找出问题所在。
同时跑三个群发上图为在三个文件夹里用网易企业邮箱同时跑三个群发

# filename: config.txt
# SMTP Login Data for Email Service
SMTPServer = smtphm.qiye.163.com (例子网易企业邮箱)
SMTPPort = 587
SMTPUsername = 你的邮箱地址
SMTPPassword = 你的邮箱密码# optional, accepts SMTPUsername if not specified
sender = 你的邮箱地址# optional, accepts SMTPUsername if not specified
confirmationEmail = 你的邮箱地址# Waiting time between emails in seconds
sleeptime = 3
# Waiting time between rounds min
pausetime = 10
# Number of emails to be sent per round, 0 for all mails
numMails = 100
# Number of rounds
numRounds = 9
  1. 在大量推送以后免不了有很多回复邮件,除了自动回复以外,还有许多退信,日积月累将让人几乎无法招架,面对一大堆没有实际意义的退信谁都会头大,大部分人可能就选择视而不见了,但这会给后人带来更大的负担,在此用Python3写了100行代码,得到下面的结果,这个文件用来批处理就非常有效,自动答复可以忽略,退信只要写个SQL命令交给CRM,下次就不会出现在群发清单里了,剩下的人工处理只是很小很小部分;在导入CRM前,必须要人工检查一下,虽然程序已经让此事已经可行了,程序还是没有那么智慧。
Sat, 13 Jul 2019 03:51:34 +0800	<postmaster@hktdc.org>	Undeliverable: 上海浦东第11期“XXXXXXXX创新创业研习班”邀请函   	pansy.y@hktdc.org
Sat, 13 Jul 2019 03:41:45 +0800	b'"'光伟b'" <123456@qq.com>'	自动回复:上海浦东第11期“XXXXXXXX创新创业研习班”邀请函
12 Jul 2019 18:08:17 +0800 (CST)	MAILER-DAEMON@mx-14-110.mail.sina.com.cn	系统退信	gxqw999@sina.cn	
12 Jul 2019 18:34:44 +0800 (CST)	MAILER-DAEMON@mx-14-109.mail.sina.com.cn	系统退信	nake98@sina.com	
12 Jul 2019 18:39:35 +0800 (CST)	MAILER-DAEMON@mx-14-106.mail.sina.com.cn	系统退信	yy7759@sina.com	
Thu, 11 Jul 2019 22:39:19 +0000	Anne <anne@hsbc.com.cn>	Automatic reply: EXTERNAL: 上海浦东第11期“XXXXXXXX创新创业研习班”邀请函
Thu, 11 Jul 2019 22:29:15 +0800 (CST)	<postmaster@usst.edu.cn>	系统退信/Systems bounce	ymzh@usst.edu.cn	
Thu, 11 Jul 2019 22:12:17 +0000	<postmaster@innovators.net>	Undeliverable: geib@ndmneb5.com
Thu, 11 Jul 2019 19:53:25 +0800 (CST)	Postmaster@126.com	系统退信	julia_july@126.com

退信处理原则上有两种方法,看上去比较智慧是上直接在服务器上读邮件,但技术难度比较大,因为要读懂邮件的具体中文内容是一件非常棘手的事情;另一种技术难度比较小的就是把服务器上的邮件用客户端全部下载下来再来分析内容,就容易操作了,用thunderbird客户端下载的文件可读性非常好,但如果有很多年积攒起来的邮件,数据量也是很惊人的,所以感觉上这个方法有点low。

最后,因为所有这些都是在SuiteCRM外面操作的,因此为了CRM信息的完整性,还要把相关的信息从数据库层面导入到SuiteCRM。

  1. 要在SuiteCRM把无效邮箱注释掉,以免下次再发;
  2. 把这次群发成功的也导入到相关的潜在客户;
  3. 把有人工回信的也导入到相关的潜在客户。


https://www.fengoutiyan.com/post/15590.html

相关文章:

  • python 发送邮件
  • python发邮件给多个人
  • python 发邮件
  • python群发邮件
  • imap和smtp的区别
  • python批量发送不同附件的邮件
  • pop imap smtp区别
  • 邮箱开启imapsmtp服务
  • 鏡像模式如何設置在哪,圖片鏡像操作
  • 什么軟件可以把圖片鏡像翻轉,C#圖片處理 解決左右鏡像相反(旋轉圖片)
  • 手機照片鏡像翻轉,C#圖像鏡像
  • 視頻鏡像翻轉軟件,python圖片鏡像翻轉_python中鏡像實現方法
  • 什么軟件可以把圖片鏡像翻轉,利用PS實現圖片的鏡像處理
  • 照片鏡像翻轉app,java實現圖片鏡像翻轉
  • 什么軟件可以把圖片鏡像翻轉,python圖片鏡像翻轉_python圖像處理之鏡像實現方法
  • matlab下載,matlab如何鏡像處理圖片,matlab實現圖像鏡像
  • 圖片鏡像翻轉,MATLAB:鏡像圖片
  • 鏡像翻轉圖片的軟件,圖像處理:實現圖片鏡像(基于python)
  • canvas可畫,JavaScript - canvas - 鏡像圖片
  • 圖片鏡像翻轉,UGUI優化:使用鏡像圖片
  • Codeforces,CodeForces 1253C
  • MySQL下載安裝,Mysql ERROR: 1253 解決方法
  • 勝利大逃亡英雄逃亡方案,HDU - 1253 勝利大逃亡 BFS
  • 大一c語言期末考試試題及答案匯總,電大計算機C語言1253,1253《C語言程序設計》電大期末精彩試題及其問題詳解
  • lu求解線性方程組,P1253 [yLOI2018] 扶蘇的問題 (線段樹)
  • c語言程序設計基礎題庫,1253號C語言程序設計試題,2016年1月試卷號1253C語言程序設計A.pdf
  • 信奧賽一本通官網,【信奧賽一本通】1253:抓住那頭牛(詳細代碼)
  • c語言程序設計1253,1253c語言程序設計a(2010年1月)
  • 勝利大逃亡英雄逃亡方案,BFS——1253 勝利大逃亡
  • 直流電壓測量模塊,IM1253B交直流電能計量模塊(艾銳達光電)
  • c語言程序設計第三版課后答案,【渝粵題庫】國家開放大學2021春1253C語言程序設計答案
  • 18轉換為二進制,1253. 將數字轉換為16進制
  • light-emitting diode,LightOJ-1253 Misere Nim
  • masterroyale魔改版,1253 Dungeon Master
  • codeformer官網中文版,codeforces.1253 B
  • c語言程序設計考研真題及答案,2020C語言程序設計1253,1253計算機科學與技術專業C語言程序設計A科目2020年09月國家開 放大學(中央廣播電視大學)
  • c語言程序設計基礎題庫,1253本科2016c語言程序設計試題,1253電大《C語言程序設計A》試題和答案200901
  • 肇事逃逸車輛無法聯系到車主怎么辦,1253尋找肇事司機