虽然现在邮件用得越来越少,但跟即时通讯相比,邮件还是有它一些独有的特质,因此某些事务中还是会用到它。
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. 把有人工回信的也导入到相关的潜在客户。

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

  1. [Python]网络爬虫(三):异常的处理和HTTP状态码的分类

    先来说一说HTTP的异常处理问题. 当urlopen不能够处理一个response时,产生urlError. 不过通常的Python APIs异常如ValueError,TypeError等也会同时产 ...

  2. python错误代码40035_[Python]网络爬虫(三):异常的处理和HTTP状态码的分类

    先来说一说HTTP的异常处理问题. 当urlopen不能够处理一个response时,产生urlError. 不过通常的Python APIs异常如ValueError,TypeError等也会同时产 ...

  3. 邮件发送退信分析大全/SMTP error, RCPT TO: 550 Relay Deny

    一般情况下,当您发送的邮件无法正常到达收件人时,mail 邮件系统将会自动给您发一封警告信,这封退信通知里面包含了无法正常发送到对方邮件地址的原因,所以绝大多数情况下可以通过退信通知来找出发信失败的原 ...

  4. 【Python SMTP/POP3/IMAP】零基础也能轻松掌握的学习路线与参考资料

    Python是一种高级编程语言,广泛应用于Web开发.人工智能.数据科学.自动化等领域.SMTP/POP3/IMAP是与邮件相关的三个协议,分别用于发送邮件.接收邮件和访问邮件.使用Python可以轻 ...

  5. 外贸软件_仿神卓外贸管理软件强大的邮件群发功能(不被退信的实现方法)

    外贸软件市场中,神卓外贸管理软件肯定是一款优秀的外贸业务流程管理软件,用户数众多,最牛的功能是邮件群发功能,今天要说的是邮件群发,常规的企业邮件一天中一般发送不会超过70封邮件,多了就有可能会被退信, ...

  6. 使用python SMTP 自动发送qq邮件

    使用python SMTP 自动发送qq邮件 参考教程 参考博客 目前有个需求就是在阿里云服务器上跑不间断抓取数据的python程序,程序有时会出现bug,因为不可能随时随地都看着程序运行,所以就想到 ...

  7. 退信之550 5.7.1 Unable to relay for *@*.com

    "550 5.7.1 Unable to relay for *@*.com"是常见的退信之一,今天我们就看看导致这个退信的真实原因,以及在mdaemon邮件服务器中遇到这个问题如 ...

  8. postfix邮件系统经典退信

    » 错误回退信结构分析 » 收件人地址不存在 » 收件人地址被禁用 » 收件人邮箱空间不足 » 邮件被对方邮件服务器过滤 » 域名解析出错 » 无法识别的命令 » 发送邮件大小超过对方邮件服务器的最大 ...

  9. 因IP被列入黑名单导致U-Mail被退信的处理方法

    邮件被退信的原因很多,这里只说国内IP自己搭建的邮件服务器,因为IP被列入黑名单而退信的解决,退信情况如图: 邮件内容类似如下: --------------------– U-Mail 投递状态通知 ...

  10. Python SMTP发送邮件

    首先在进入正题之前,我们需要对一些基本内容有所了解:常用的电子邮件协议有SMTP.POP3.IMAP4,它们都隶属于TCP/IP协议簇,默认状态下,分别通过TCP端口25.110和143建立连接.Py ...

最新文章

  1. oracle 树 向上查询,Oracle中显示树结构查询语句【子查父和父查子】
  2. python装饰器函数-python函数装饰器
  3. input标签加disabled属性后无法获得其value值
  4. 面试前需要准备的五个步骤
  5. 关于Gossip协议的几个问题
  6. JavaScript学习(十二)—removeAttribute方法、hasAttribute方法、createAttribute方法以及setAttributeNode方法
  7. 「每天一道面试题」谈String和StringBuffer、StringBuilder区别
  8. SQL查询分析器SQL语句导入TXT文件
  9. 算法设计与分析——斐波那契堆
  10. 大学生河南旅游网页设计成品 学生网页作业我的家乡网页制作代码 静态HTML旅游网页设计作品下载
  11. 计算机窗口的排列和切换,win10系统操作多窗口显示排列切换的方案介绍
  12. 笔记本html5播放没有图像,HTML5视频标签无法在Lightbox中使用(HTML5 Video: autoplay not working in lightbox)...
  13. win7计算机里桌面菜单没有反应,win7系统桌面任务栏假死无反应的设置办法
  14. bos平台 Java_Bos平台修改发布业务单元的jdk版本
  15. LCD自适应LED背光控制技术
  16. 解决pdf.js下载PDF文件名称中文乱码和不带.pdf后缀名问题
  17. 【预定义】C语言预定义代码(宏、条件编译等)内容介绍【最全的保姆级别教程】
  18. [TMI2019-06]3D Auto-Context-Based Locality Adaptive Multi-Modality GANs for PET Synthesis
  19. vite安装失败问题Emitted ‘error‘ event on ChildProcess instance at:-4058esbuild.exe
  20. 详解Android屏幕亮度和息屏的实现

热门文章

  1. 【毕设教学】 经典单片机控制算法:PID - 嵌入式 物联网
  2. 网站开发的需求分析报告
  3. 一笔四线连9点你可以吗
  4. 40个全球免费开放的电子图书馆
  5. 卡西欧手机计算机软件,卡西欧5800计算器手机版app
  6. Delphi实现通用的定时自动关机程序
  7. 平行实境游戏设计——《家务战争》初稿
  8. Unity基础补漏(1)_GameObject类_Time类_Transform类_Camera_光面板_物理面板/物理材质_碰撞检测函数_刚体加力
  9. 查看Mysql数据库版本
  10. EFK家族---Fluentd日志收集