通过 PHP 向许多用户发送邮件的安全方式

Posted

技术标签:

【中文标题】通过 PHP 向许多用户发送邮件的安全方式【英文标题】:Safe way to send mail via PHP to many users 【发布时间】:2011-04-18 17:13:31 【问题描述】:

让我解释一下我在标题中的意思。假设,例如,我正在为一个网上商店/目录创建一个小型电子商务系统。客户可以选择是否希望接收时事通讯。如果他们这样做了,那么从逻辑上讲,应在时事通讯形成并准备好时立即发送时事通讯。

当然可以通过从数据库中获取所有指定的用户电子邮件并使用for循环通过mail函数在循环中发送邮件来简单地完成,但问题是有人告诉我,这是不好的做法.简单且不便宜的方法是购买互联网服务来发送时事通讯,但是对于 php 程序员来说需要什么?

所以我问你们谦虚的同志,从你们的角度来看,什么可能是一个解决方案?

注意!你可能不会相信我,但这不是垃圾邮件。

UPD:我可能对自己的解释有误,但我希望听到的解决方案不仅涉及正确的邮件发送方式,还涉及正确的投递方式。由于并非每次发送的邮件都始终送达。 当然也有一些不可预测的原因。例如,沿途某处发生了故障,邮件丢失(如果可能的话),但也有其他原因可能受到服务器或其他地方的影响。或许有必要和宿主商量一下?

【问题讨论】:

下载这个 OpenSource 购物车系统:OpenCart 然后在源文件中查找mail.class.php 文件,这是系统的抽象,因此您可以直接使用该文件如你所愿,非常好,支持SMTPPHPMail @RobertPitt,但是如果例如我的系统所有者将尝试向订阅者发送新闻,是否会有任何形式的控制,是邮件传递还是只是发送?你知道这一点很重要。一件事是发送邮件TRUE,另一件事是让客户收到邮件? 将外发电子邮件保存到您自己制作的发件箱或电子邮件客户端。在我的项目中,我使用了一个函数调用队列,它兼作发件箱。使用相应的参数保存函数调用(例如 sendThisEmail)并稍后调用它们,允许重试等。 【参考方案1】:

没有理由不能用 PHP 编写它,尽管我不会将它作为 webrequest / HTTP 进程的一部分。我已经成功地实现了每次邮件发送或接收 500,000 个订阅者(取决于可用的本地数据,因为这是一个特定于位置的项目)。这是一个内部项目,所以很遗憾没有适合你的代码/包,但我遇到了一些建议:

设置递送

从 phpmailer 本身开始,负责格式化、内容和标题的编码、添加附件等。它的那部分运行良好,我不想从头开始编写。 电子邮件本身的“发送”只是在数据库中设置一些标志,是否/如何/应该向(部分)订阅者发送什么。 设置此标志后,它将由 cronjob 自动获取,不再涉及网络服务器。 我从一个严重污染的数据库开始,其中包含数百万个电子邮件地址,其中 lot 显然是无效的,所以首先要验证所有电子邮件地址的格式,然后是主机: filter_var($email, FILTER_VALIDATE_EMAIL); 超过订阅者(并且显然存储了结果)删除了最初的几十万封无效电子邮件。 从电子邮件中分离出主机(并存储主机名),并验证它(在 DNS 中是否有 MX 或至少 A 记录,但请记住:您可以将电子邮件发送到 IP 地址foo@[255.255.255.255],所以请确保这些地址有效))摆脱了更多。此处的电子邮件地址没有被永久禁用,但带有一个状态标志,表明它们由于域名/IP而被禁用。 脚本已更改为要求订阅时/插入之前有效的电子邮件地址,这种废话'你不会在任何地方得到它@anywhere'数据库中的订阅污染只是荒谬。 现在我得到了一份可能有效的电子邮件地址列表。本质上有 3 种方法可以检测无效地址(请记住,all 可能是临时的): 服务器立即拒绝它们。 之前确定的服务器只是不监听流量。 在您认为已送达之后很久,它们就会被退回。 奇怪的是,退回邮件,每个电子邮件服务器似乎都有另一种格式,一开始很难解析,但实际上使用VERP 很容易捕获。与其解析整个电子邮件,不如将专用电子邮件地址(我们称之为 mailer@example.com)配置为而不是传递到邮箱,通过命令将其通过管道发送,如果我们将电子邮件发送到 user@server.tld,@为mailer+user=server.tld@example.com 设置了987654324@。在收据上轻松解析,在多少次退回(邮箱不存在,邮箱可能已满(是的,仍然!)等)之后,您声明一个电子邮件地址不可用取决于您。 现在,服务器直接拒绝。可能我们可以正确配置一些 MTA 和/或为它们编写插件,但由于电子邮件是时间敏感的,我们必须对每个邮件在最后可用的交付时间进行绝对可配置的控制(之后电子邮件不再用户感兴趣的),每个接收服务器的节流,一般来说,这将花费大约相同的时间用我们知道的 PHP 编写一个邮件程序,它使用 SMTP 协议直接连接到接收服务器上的套接字 25。只需最少的努力,就可以使用另一种传输方式,然后 PHPMailer 中的默认选项是内置的。 SMTP 协议其实很简单,但有一些注意事项: 很多接收服务器都应用灰名单:大多数垃圾邮件机器人不会真正关心特定邮件是否到达,他们只是将它们大量发送出去。因此,如果未知/尚未信任的发件人发送邮件,它将被暂时拒绝。抓住它(通常是代码 451),然后将电子邮件放入队列中以便稍后重试。 邮件服务器,尤其是大型 ISP 和免费服务(gmail、hotmail/msn/live 等)的邮件服务器不会支持大量邮件而不进行反击:在最初的几百 / 千之后,他们开始拒绝你。稍后再详细介绍。

加快速度

现在,我们有一个有效的交付系统,但它需要快速。如果您只有 10,000 个地址要发送到,那么在一小时内发送 10,000 封电子邮件就可以了,但我们要求的最低要求是每小时 200,000 封。一开始是一个专用服务器(实际上它的功率非常低,无论您做什么,发送电子邮件所花费的大部分时间都在网络中,而不是在您的服务器上)。 IP 缓存:还记得我们从电子邮件地址中的主机名请求的所有 IP 吗?我们显然存储了这些,并且一次又一次地查找他们的 IP 会导致相当大的延迟。但是,IP 可能会发生变化:那里的 DNS 记录,另一个地方的另一个 MX……数据很快就会过时。大多数情况下,服务器实际上并没有发送任何东西(订阅时事通讯显然是突发的),一个低优先级的 cronjob 正在运行检查所有具有陈旧 IP 的主机名(我们选择较早的 1 天作为陈旧的 IP 地址) ,包括那些以前没有的(新域总是被注册,那么为什么不应该在某人已经热情地使用他/她的全新电子邮件地址订阅后的第二天就可以使用域?或者服务器解决了某些域的问题等)。实际上现在发送电子邮件不再需要域查找。 重用 SMTP 连接:当您直接与端口 25 通信时,设置与服务器的连接需要相当大一部分时间来传递电子邮件。您不必为每个服务器都设置新连接电子邮件,您可以通过同一连接发送下一封电子邮件。一些跟踪和错误导致此处将默认设置为每个连接大约 50 封电子邮件(假设您有那么多或更多的域)。但是,在电子邮件地址失败时,关闭并重新打开连接以重试有时会有所帮助。总而言之,这真的有助于加快进展。 一个明显的,太明显了,我差点忘了提它:必须在现场创建电子邮件的正文是一种浪费:如果是普通邮件,请准备好正文(我将 PHPMailer 更改为能够使用缓存的电子邮件),可能几天前(如果您知道您将在周五发送邮件,而您的服务器处于空闲状态,为什么不在周三准备好它们?如果是个性化的,如果有足够的时间,您仍然可以提前准备好,如果没有,至少要等待非个性化部分。 多个进程。我是否提到过发送电子邮件所需的大部分时间都花在了网络上?一个邮寄过程几乎没有从您的电子邮件服务器中获得最大收益,几乎没有明显的负载,并且邮件正在慢慢流出。尝试使用多个进程向队列的不同部分发送邮件,以查看适合您的服务器/连接的内容,但请记住 2 件非常重要的事情: 不同的进程使您非常容易受到竞争条件的影响:绝对确定您拥有一个完全验证系统,该系统永远不会发送相同的邮件两次(三次,甚至更多) )。它不仅严重惹恼了用户,而且您的垃圾邮件也更上一层楼。 尽可能将域保持在一起:从队列中随机选择,您将失去与接收域电子邮件的服务器保持开放连接的优势。

避免拒绝

您将发送大量邮件。这正是垃圾邮件发送者所做的。但是,您不想被视为垃圾邮件发送者(毕竟,您不是,是吗)?有多种机制可以彻底提高您对接收服务器的信任度: 有一个适当的反向 DNS:如果二级域匹配,则处理检查属于发送电子邮件的 IP 的 DNS非常:您是否代表 发送邮件example.com?确保您的服务器的反向 DNS 类似于 somename.example.com。 为您的域发布 SPF 记录:明确指出用于发送批量电子邮件的计算机允许并希望发送带有该 From / Return-Path 标头的邮件。 记住拒绝:服务器不希望它一次又一次地告诉您不存在不同的电子邮件地址。当我们处理所有确实(不再)存在的未经验证的电子邮件地址时,无论是自动化机制,甚至是人工管理员,都阻止了我们的服务器。直到后来我们才采用双重选择加入,因此数据库被拼写错误、人们切换 IP 以及电子邮件地址、恶作剧电子邮件地址等污染。一定要捕获那些无效的,如果有足够多或严重的故障,取消订阅它们。他们对你没有好处,他们在占用资源,如果他们真的想要你的邮件并且邮箱稍后可用,他们只需要重新订阅。 DKIM 是另一种可以提高您的可信度的机制,但由于我们尚未实施它(目前),我不能告诉您太多。 MX 记录:如果您的发送服务器也是域的接收服务器,一些服务器仍然喜欢它。与当时一样,我们只有 1 个 MX,并且由于邮件服务器仍然不是很忙,我们将其称为域的后备 MX 服务器。普通的 MX 服务器不是发送订阅的服务器,因为您尝试向(客户端等)发送重要电子邮件的服务器暂时阻止它是非常烦人的,因为您已经发送了一堆不太重要的邮件。它确实具有接收 MX 的最高偏好,但如果它失败了,我们有一个很好的好处是我们的订阅发送服务器仍将作为交付的后备,所以在危机中我们仍然可以得到它,防止尴尬的反弹客户尝试联系我们。 告诉他们关于你的事。严重地。免费电子邮件地址(如 live.com)的许多主要参与者为您提供了以某种方式注册的机会,或者如果您的电子邮件被拒绝,可以通过一些联系人寻求帮助和支持。我有合理的理由发送这么多电子邮件,并且可以相信您有这么多订阅者,他们很可能会严重增加您每小时可以发送到他们服务器的电子邮件数量。如果你足够有说服力和诚实,那么微不足道的 1,000 可能会变成一万甚至更高。可能存在您必须履行的合同、要求以及您必须做出(并遵守)的承诺才能被允许这样做。 ISP 是一个独立的品牌,每个其他玩家都是不同的。通常不要打扰他们,因为在 99% 的情况下,您能找到的唯一号码只会有愿意为您的互联网连接进行故障排除的人,他们几乎不了解(或被允许)其他方面。 abuse@ 电子邮件地址是一个很好的起点,但看看您是否可以从某个地方深入研究一个更中肯的电子邮件地址。准确、诚实和完整:大约有多少订阅者拥有该 ISP 的电子邮件地址,您尝试邮寄他们的频率,您收到的错误或拒绝是什么,订阅和取消订阅过程如何,以及什么是您实际向他们的客户提供的服务。另外,请善待:发送这些邮件对您的业务有多么重要,对此感到恐慌并声称遭受可怕的损失与他们无关。礼貌地陈述事实和愿望,并询问他们是否可以提供帮助,而不是要求解决方案。 限制:尽管您尝试过,但某些服务器每小时和/或每天只接受一定数量的邮件。了解这些数字(无论如何,我们都会记录成功和失败),将它们设置为正常域的合理默认值,将它们设置为针对更大玩家的商定限制。

避免被标记为垃圾邮件

第一条规则:不要发送垃圾邮件! 第二条规则:永远!不是“一次性”,也不是“他们没有订阅,但这对他们来说可能是一生难忘的交易”,不是出于好意,人们不得不向您索要您的电子邮件。 显然设置了正确的双重选择订阅机制。 PHPMailer 会自行设置正确的标头, 通过网络设置一个简单的取消订阅机制(在每封邮件中包含指向它的链接),如果有的话,还可以通过电子邮件和客户服务。确保客户服务可以直接取消订阅。 如前所述:取消订阅(过度)失败并退回。 避免使用垃圾邮件的“终身交易”措辞。 在您的电子邮件中谨慎使用网址。 避免添加指向您无法控制的域的链接,除非您完全确定可以信任它们不会发送垃圾邮件,即使那样... 为用户提供价值:被 google/yahoo/live webmail 客户端中的用户交互标记为垃圾邮件严重损害未来的成功(在网站上说明:如果您注册,live/msn/hotmail 将转发所有由您的域发送给您的邮件被用户标记为垃圾邮件。学会喜欢它,并且一如既往:退订他们,他们显然不想要您的商城并且正在损害您的垃圾邮件评级)。 监控您的 IP 的黑名单。如果您出现在其中一个上,那就是再见了,因此需要立即采取行动来清除您的名字确定案件。

测量成功率

在您控制整个过程的情况下,您有理由确定电子邮件在某处结束(尽管它可能是 MX 的 bitbucket 或垃圾邮件文件夹),或者您记录了失败及其原因为什么。这会处理“实际交付”的数字。 有些人会试图说服您将在线图片的链接添加到您的电子邮件中(真实的或著名的 1x1 透明 gif 图像),以衡量有多少人实际阅读了您的电子邮件。由于高比例会阻止这些图像,这些数字充其量是不稳定的,我们认为我们不应该打扰它们,它们的数字完全不可靠。 如果您希望用户做某事,那么衡量实际成功率的最佳选择会容易得多。向邮件中的链接添加参数,这样您就可以衡量有多少用户到达了您链接的网站,他们是否执行了所需的操作(观看视频、发表评论、购买商品)。

总而言之,包括所有日志记录、用户界面、每个域/电子邮件/用户的可配置设置等。我们花了大约 1.5 个人工月来构建和消除这些怪癖。与外包电子邮件相比,这可能是一笔不小的投资,也可能不是,这完全取决于数量和业务本身。

现在,让我开始说我是一个用 PHP 编写 MTA 的傻瓜,我非常喜欢它(这是我写这么多文本的原因之一),以及极其通用的日志记录和设置功能,基于故障百分比等的每台主机警报正在变得如此简单;)

【讨论】:

【参考方案2】:

使用类似Swiftmailer、PHPmailer 或Zend_mail 是使用简单mail() 函数的更好替代方法,因为它很容易被标记为垃圾邮件。需要考虑的邮件问题太多了——其中大部分问题都可以通过使用预先存在的库来解决。

手动发送群发邮件需要解决的几个问题:

使用不正确的标题。

处理退回的邮件

由于大量电子邮件而导致脚本超时。

编辑:

可能不是您要寻找的答案。但是,我强烈建议您投资Campaign Monitor 或Mail Chimp。由于此过程不是出于教育目的,而是出于商业目的,我会强烈推荐上述服务。

【讨论】:

哇。这是巨大的!希望我能超过 +1【参考方案3】:

我收到了你的问题,但在回答之前,让我先谈谈通常的考虑。首先,我强烈推荐使用像 Mail Chimp 这样的服务。它对于小型工作来说有点免费,并且有许多很酷的功能,比如跟踪打开了多少电子邮件,点击了多少,发送失败了多少……想想帮自己一个忙,不要重新发明***。

现在,为了了解目的,我们来回答您的问题。

首先要牢记的是,确保您的清单是一个好的清单。怎么做?好吧,对于一个好的列表,我的意思是一个有效的电子邮件地址列表。只需在您的页面上放置一个时事通讯表单,只有一个字段(可能是验证码,但我认为没有必要)。

将所有输入保存到数据库表中,字段“isValid”默认设置为 false,以及任何类型的唯一哈希。然后,您发送一封确认电子邮件,其中包含一个用于确认的链接(生成的哈希),单击该链接将使“isValid”标志为真,以及一个取消链接(始终在您的所有电子邮件中发送此取消链接)。

这就是商店和严肃网站所做的。任何强迫您的客户/访客接收的东西都是一种不良的道德行为(即垃圾邮件)。

第二件事,使用良好的托管服务。垃圾邮件发送者通常使用太便宜的服务,并且主要的电子邮件服务将来自这些地址的所有内容列入黑名单。

我知道,如果我弄错了你的问题,你应该问问自己。不,我不知道,技术性的东西现在来了。

为什么将邮件函数放在 for 循环中是一种不好的做法?简单的。因为函数 mail 每次调用它都会做几个操作。 PHP,将打开一个与邮件服务器的连接,发送要解析的数据,请求发送,注册邮件服务器状态,关闭连接,冒泡状态以完成您调用的邮件功能并清理内存。

从编程的角度来看,这种连接开销是人们所说的不良做法的问题。使用 SMTP/IMAP 解决方案更好,因为它优化了这个过程。

我看到了您对交付的问题,对技术内容有点失望。好吧,正如我所说,您有一些方法可以确保您的电子邮件列表足够好。但是如果发生另一个异常,比如客户服务器出现停电+不间断故障怎么办?

嗯,PHP 保持“请求邮件服务器发送,邮件服务器发送”的状态。如果邮件服务器发送了您的消息,PHP 将返回 true。期间。

如果客户无法接收或拒绝,您应该检查电子邮件标题和电子邮件状态。这些在电子邮件服务器上。再一次,这些信息可以通过 SMTP/POP/IMAP 扩展访问,而不是邮件功能。

如果您想走得更远,请阅读 IMAP 文档,搜索电子邮件课程(phpclasses.org、pear 和 pecl 是最好的研究地点)。

额外提示:RFC 可能很有用,因为您可以更好地了解电子邮件服务器真正相互通信的内容。

额外提示 2:访问您的 gmail 或 ymail 并检查您发送/接收的消息的“完整版本”并阅读其标题。你可以从他们那里学到很多东西。

【讨论】:

【参考方案4】:

通过 SMTP 身份验证:http://www.cyberciti.biz/tips/howto-php-send-email-via-smtp-authentication.html

【讨论】:

我不认为这有很大的不同,只要 phpmail 配置正确就一样好。【参考方案5】:

只需使用PHP Mail并学习IMF以及如何构建自定义标头您可以附加第四个参数,示例如下

<?php
// multiple recipients
$to  = 'aidan@example.com' . ', '; // note the comma
$to .= 'wez@example.com';

// subject
$subject = 'Birthday Reminders for August';

// message
$message = '
<html>
   ...
</html>
';

// To send HTML mail, the Content-type header must be set
$headers  = 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";

// Additional headers
$headers .= 'To: Mary <mary@example.com>, Kelly <kelly@example.com>' . "\r\n";
$headers .= 'From: Birthday Reminder <birthday@example.com>' . "\r\n";
$headers .= 'Cc: birthdayarchive@example.com' . "\r\n";
$headers .= 'Bcc: birthdaycheck@example.com' . "\r\n";

// Mail it
mail($to, $subject, $message, $headers);
?>

来源:http://php.net/manual/en/function.mail.php

【讨论】:

无论您的电子邮件多么正确,如果尽快循环发送,他们将禁止您。必须安排。【参考方案6】:

创建一个邮件队列子系统,其中可能包括诸如 mail_queue、mail_status、mail_attachments、mail_recipients 和 mail_templates 等表......

【讨论】:

【参考方案7】:

你可以考虑 PHPMailer http://phpmailer.worxware.com/index.php?pg=exampleasendmail

您可以添加多个收件人和一个特殊的回调函数来处理每封已发送邮件的返回消息。 (例如访问链接)

我不认为通过 php 捕获“邮件传递失败”错误邮件是可能的,除非您通过 SMTP 使用 PHPMailer,并且偶尔从您的外发电子邮件集合中查看来自任何收件人的任何返回消息。

【讨论】:

好的,但我想正如我之前提到的,我说的不仅仅是 PHP 解决方案。我知道,SO 或多或少是用于编程解决方案,但也许这个问题需要服务器人员来解决?没有?

以上是关于通过 PHP 向许多用户发送邮件的安全方式的主要内容,如果未能解决你的问题,请参考以下文章

如何在 php 中向数千名用户发送安全(爆炸)电子邮件?

企业需要通过 PHP 自动通过电子邮件发送安全信息

使用php发送电子邮件(phpmailer)

是否可以使用 PHP 异步发送电子邮件,同时向用户提供有关交付的反馈?

PHP 向用户在插件文件中输入的地址发送电子邮件

如何在 PHP 中向用户发送每日电子邮件通知?