时令问候群发邮件
在我为 Linux Journal 撰写的上一篇文章中,(https://linuxjournal.cn/content/best-wishes-new-year) 我分享了一些我的新年决心。其中一个决心是更经常地与我的朋友和家人交流。在本文中,我将描述我为实现这一目标而采取的第一个步骤之一。
几个月前,我离开了一份工作了 14 年的工作,因此我在工作中积累了很多我关心的人。当我向许多即将成为前同事的人告别时,我承诺尽快给他们发一封电子邮件,让他们知道我的近况。就像我说的那样,那是几个月前的事了,但这封信从未发出;生活似乎总是妨碍着我,我一直没有抽出时间来做这件事。
我知道我没有时间给我的每个朋友写一封单独的信。做旧的复制粘贴实际上非常耗时,而且比你想象的更容易出错;很容易忘记添加主题,或者没有将所有消息复制到剪贴板等等。我需要的是一种高效、防错的方法,向我的所有朋友和家人发送电子邮件。我是一名程序员,所以解决方案显而易见。我明白,外面有一些不择手段的人,他们已经利用同样的解决方案建立了一种商业模式,但这并不是我的意图。我只是不想手工写 100 封邮件地址,或者更糟糕的是,写那么多信封!
正如我之前写过的,(https://linuxjournal.cn/content/managing-your-life-egroupware) 我使用 E-Groupware 来存储几乎所有对我来说重要的信息。所以,我有一个现成的朋友及其电子邮件地址列表。我想我可以使用 EGW 的电子邮件分发机制,但我不使用 EGW 的电子邮件客户端,并且想要一些更易于管理的东西。我最终做的是创建了一个新的联系人类别。然后我将这个类别分配给我想要接收我的新闻通讯的人。随着时间的推移,我将能够使用 EGW 管理我的“圣诞贺卡列表”。真不错。
编写一个说 SMTP 协议的程序是一回事。我的程序需要为各种邮件客户端正确格式化消息。更糟糕的是,我想发送一条简短的文本消息,并将我的新闻通讯作为 .pdf 文件附件。我还想附上一张家庭照片作为 .jpg 文件。我最初的想法是这将很困难,即使在 perl 中也是如此。天哪,我错了!事实证明,通过使用 MIME::Lite 模块,这几乎是微不足道的。让我们看一些代码。
1 #!/usr/bin/perl 2 3 use DBI; 4 use MIME::Lite; 5 6 $dbh = DBI->connect("dbi:Pg:dbname=db_name;host=example.com”, "db_user", "passwd"); 7 8 $sth = $dbh->prepare("select cat_id from egw_categories where cat_name='Christmas'"); 9 $sth->execute(); 10 ($id) = $sth->fetchrow_array(); 11 12 open FILE, "./2008.txt"; 13 @data = <FILE>; 14 $data = join("", @data); 15 16 $clause = "(cat_id = \'$id\' or cat_id like \'$id,%\' or cat_id like \'%,$id,%\' or cat_id like \'%,$id\')"; 17 18 $sth = $dbh->prepare(" select contact_email from egw_addressbook where $clause 19 and contact_email is not null 20 union 21 select contact_email_home from egw_addressbook where $clause 22 and contact_email_home is not null"); 23 $sth->execute(); 24 25 while ((@a) = $sth->fetchrow_array()) { 26 print "X: $a[0]\n"; 27 28 $a[0] = "mdiehl\@diehlnet.com"; 29 30 $message = MIME::Lite->new( 31 From => "mdiehl\@diehlnet.com", 32 To => $a[0], 33 Subject => "Greetings!", 34 Data => $data, 35 ); 36 37 38 $message->attach( 39 Type => "application/pdf", 40 Encoding => "base64", 41 Path => "./2008.pdf", 42 Filename => "2008.pdf" 43 ); 44 45 $message->attach( 46 Type => "image/jpeg", 47 Encoding => "base64", 48 Path => "./2008.jpg", 49 Filename => "2008.jpg" 50 ); 51 52 MIME::Lite->send("sendmail", "smtp.example.com", timeout=>20); 53 54 $message->send; 55 }
程序第 1-7 行只是样板代码。在第 8-10 行中,我查找名为“Christmas”的联系人类别的索引号。我们稍后会使用该索引号,因此我们将其保存在一个变量中。
第 12-14 行是我读入一个外部文件,用作我之前提到的简短消息。这种方法允许我使用我最喜欢的编辑器创建文件,甚至在发送之前对其进行拼写检查。我也无需在程序中硬编码我的消息,这是一举两得。
我稍后会回到第 16 行,但首先让我们谈谈第 18-23 行中的 SQL。我的一些朋友有工作电子邮件地址,一些朋友有个人电子邮件地址。有些人两者都有。EGW 允许我分别存储这些地址。我想编写一个单独的 SQL 查询,将所有这些地址提取到单个列中,以便我的 perl 脚本更直观。我所做的是为家庭地址编写一个查询,为工作地址编写一个查询,并取两个查询的并集。大部分选择逻辑都与我在第 16 行定义的 $clause 变量绑定在一起。
我讨厌 EGW 将类别与联系人关联的方式。EGW 使用 cat_id 字段来存储联系人所属类别的逗号分隔列表,而且该列表甚至没有排序。因此,在第 16 行,我构建了一个子句,在 cat_id 字段中查找我们的类别 ID,无论它可能出现在该字段中的哪个位置。该值可能是列表上唯一的,在这种情况下,将没有逗号。它也可能是列表上的第一个、中间或最后一个条目。我将此子句分配给一个变量,因为 SQL 并集的两半都需要相同的逻辑,并且简单地复制和粘贴它会模糊逻辑。它很丑陋,但它有效。现在你知道它应该做什么,就不是太难理解了。
在第 25 行,我们开始循环遍历每个记录,以便我们可以向他们发送电子邮件。第 26 行只是一个打印语句,这样我可以确保程序运行正常。第 28 行更有趣一些。在这里,我用我自己的电子邮件地址覆盖了从数据库中获取的数据。这是一个调试技巧。我不想调试这个程序,并且冒着向我声称关心的人发送一堆垃圾邮件的风险。就在我发出信件之前,我删除了这一行,程序按预期工作... 并经过了测试。
在第 30-35 行中,我们使用 MIME::Lite 模块创建一个新的电子邮件消息。我们还为 From:、To: 和 Subject: 标头分配值。我们之前读入的文本消息在此时使用 Data 字段放入电子邮件中。
然后我们在第 38-50 行创建 2 个文件附件。第一个是 .pdf 文件,第二个是 .jpg 文件。关于这部分代码,有几件事值得注意。首先,我必须为每个附件分配一个 mime 时间,以及一个编码方案。从查看代码来看,这部分非常直观。
关于这些附件,另一件值得一提的事情是,我能够指定我想要附加的外部文件的名称,而不必自己读取文件。此外,我还可以为附件指定一个“建议的”文件名,以防我的某个朋友实际上想要将该文件保存到他们的硬盘驱动器。
第 52-55 行是我将消息发送出去并关闭主循环的地方。我最初尝试使用我自己的邮件服务器作为网关,但遇到了问题。由于某种原因,我的服务器拒绝中继消息。当然,我想成为一个好的网络公民,而不是一个开放的中继,但我应该能够使用我的服务器发送常规电子邮件。我从未解决这个问题,只是使用了我的 ISP 的中继。
当我在考虑这个程序时,我有点担心我的朋友可能会觉得我本质上写了一个群发邮件程序,以便向他们发送一封本来就缺乏人情味的电子邮件问候,这有点俗气。到目前为止,似乎没有人注意到,或者说没有人介意这条消息不是直接写给他们的。我收到了回复,表明他们很高兴收到我的消息并喜欢这张家庭照片。总的来说,我认为这个过程是成功的。还有额外的好处,就是程序和联系人列表已经准备好,下次我想向我的朋友或客户问好时就可以使用了。