[返回]
计算机世界2000年第6期

如何批量发送电子邮件

北京大学信息管理系 黄 剑

  本文将实现如何编写简单的应用程序向邮件订阅者发送内容一样或者内容基本一样的电子邮件。

  一、软件准备

  读者应该拥有一份C ++Builder 3.0 或者Delphi 4.0 或以上版本的拷贝。另外,还要有一份支持ODBC 接口的数据库拷贝,例如:Microsoft Access、Paradox、dBase、Microsoft SQL Server、IBM DB2、Oracle 等等。该数据库可以是平面文件数据库,也可以是关系型数据库。在本文中使用的是Delphi 5.0 和Microsoft Access。

  二、数据库设计

  该email 数据库中共有三个表:User 表,Subject 表,link 表,各个表的字段格式如下:
de074_1.jpg (18450 字节)
  其中的外键引用关系如下:
de074_2.jpg (27955 字节)
de074_3.jpg (21044 字节)
de074_4.jpg (48911 字节)
  link 表的UserID 字段引自User 表的ID 字段,Subscribe 表的SubjectID 字段引自Subjectx 表的ID 字段。

  如果您使用的数据库支持Trigger,mjh yid d Insert、Update、Delete 的Trigger 使User 表和Subject 表的ID 字段具有唯一的值。

  三、程序设计过程

  本程序需要的组件有:TdataBaseTquery、NMSmtp、TListBox 组件。其中,Tquery 组件负责数据库的查询工作,TdataBase 负责数据库的连接工作,TListBox 组件用来显示查询结果,NMSmtp 组件用来发送E -Mail 信息。
  本程序实现的功能有:

根据用户订阅的邮件主题,从数据库中查询出要发送的用户名称,电子邮件地址;
在已经查询出的用户中进行进一步的调节;
载入邮件正文,保存邮件正文;
发送邮件;
进行邮件发送状态的记录并保存。

  四、具体的设计细节

  本程序的VCL 组件的属性:
  DataMOdule1,数据窗口。

  此外,还需要三个eidt 文本框(分别输入发信人姓名、发信人邮件地址、邮件主题),及一个memo 邮件正文输入域。

  Form2 窗口,用户配置界面。

  需要注意的是,其中的Groupbox1 和Groupbox2 的Visible 设为false,在查询出用户信息后再显示这些组件。

  1. 查询数据库

  在配置用户信息的窗体的FormCreat 事件中,需要将数据库中的所有的邮件主题查询出来,显示在一个ComboBox 组件中。其主要代码如下:
de074_4.jpg (48911 字节)
de074_5.jpg (53784 字节)
de074_6.jpg (22117 字节)

  procedure TForm2.FormCreate(Sender: TObject);
var
 i:integer;
begin
Query1.Active:=false;
// 在对TQuery 组件进行查询之前,
必须要关闭TQuery 组件,或者
将其属性Active 设为false;
  Query1.Active:=true; // 打开TQuery 组件
  for I:=0 to Query1.RecordCount -1 do
  begin
   ComboBox1.Items.Add(Query1.FieldValues
['subject']); // 将查询主题加入到TComboBox 中
    Query1.Next;
  end;
  Query1.First; // 将TQuery 的记录设为第一条
  ComboBox1.Text:=ComboBox1.Items[0];
end;
  然后再根据ComboBox1 中的主题查询用户和用户邮件地址。

  procedure TForm2.Button1Click(Sender: TObject);
var
 i:integer;
begin
  listbox1.Clear;
  listbox2.Clear; // 清除两个ListBox 中的内容,
         进行新的一次查询
  Query1.Active:=false;
  Query1.SQL.Clear;// 清除TQuery 的SQL 语句
  if not checkbox1.checked then
  // 如果选择发送给订阅相应主题的用户
  begin
  Query1.SQL.Add('SELECT user.username,
  user.address, subject.subject ' +
  'FROM (link INNER JOIN subject ON link.subjectid
   = subject.subjectid) INNER JOIN user ON link.userid
   = user.userid ' +
  'WHERE (((subject.subject)=:subject))');
  Query1.Params[0].AsString:=ComboBox1.Text;
   // 设置SQL 中的主题参数
  end
  else // 如果选择发送给全部用户
  Query1.SQL.Add
  ('select user.username,user.address from user');
  Query1.Prepare;
  Query1.Open;
  for i:=0 to Query1.RecordCount -1 do
  begin
ListBox1.Items.Add(expend(vartostr(Query 1
.fieldvalues['username'])) +vartostr
  (Query1.FieldValues['address']));
  // 在订阅用户框中显示查询到的用户信息
    Query1.Next;
  end;
  label4.caption:='共有' +inttostr
   (listbox1.Items.Count) +‘项';
  label5.Caption:='共有' +inttostr
   (listbox2.Items.Count) +‘项';
  // 显示查询到的用户数量
  GroupBox3.Visible:=true;
  GroupBox2.Visible:=true;
  Form2.Height:=554;
end;
  2. 调整用户信息

  双击需要发送的用户框中的条目,将其去掉;同样,双击不需要发送的用户框中的条目,将其加入发送用户框中,这部分代码相当简单,在此略去。

  3. 保存、载入邮件正文

  本程序中邮件正文是显示在TMemo1 中的,保存和载入邮件正文可以使用TMemo1.lines.loadfromfile(string filename) 方法及TMemo1.lines.savetofile(filenname) 方法。实现代码如下:

  载入邮件正文:

procedure TForm1.Button2Click(Sender: TObject);
begin
  if (datasmtp.DataModule1.OpenDialog1.Execute) then
  begin
Memo1.Lines.LoadFromFile(datasmtp.DataModule
  1 .OpenDialog1.FileName);
  end;
end;
  保存邮件正文:
procedure TForm1.Button3Click(Sender: TObject);
begin
  if (datasmtp.DataModule1.SaveDialog1.Execute) then
  begin
    Memo1.Lines.SaveToFile(datasmtp.DataModule
  1.SaveDialog1.FileName);
  end;
end;
  4. 发送邮件

  发送邮件使用的是Delphi 和C ++Builder 中的VCL 组件TNMSMTP,在此组件中封装了发送邮件的各种属性、方法和事件,其中PostMessage 包含将要发送的邮件的相关信息,PostMessage 中包括Attackments( 邮件附件信息)、body( 邮件正文)、Date( 邮件发送日期)、FromAddress( 发信人邮件地址)、FromName( 发信人姓名)、LocalProgram( 发送邮件程序的名称)、ReplyTo( 回信地址)、subject( 邮件主题)、ToAddress( 收信人地址)、ToBlindCarbonCopy( 邮件暗送地址)、ToCarbonCopy( 邮件抄送地址)。此外,TNMSMTP 的重要属性和方法有:Connected( 是否与邮件服务器连接上)、Host( 指定邮件服务器地址)、Port( 端口号,默认为25)、Connect( 与Host 指定的服务器连接)、sendmail( 发送邮件)。

  发送邮件的代码如下:

procedure TForm3.Button3Click(Sender: TObject);
var
 i:integer;
begin
   with datasmtp.DataModule1.smtp.PostMessage do
   begin
     FromAddress:=form1.edit2.Text;
     FromName:=form1.edit1.Text;
     Subject:=form1.edit3.text;
     Body.Text:=form1.memo1.Text;
   end;
with datasmtp.DataModule1.smtp do
begin
 for i:=0 to form2.ListBox1.Items.Count -1 do
 begin
   PostMessage.ToAddress.Clear;
   PostMessage.ToAddress.Add
    (getaddress(form2.List Box1.items[i]));
   Host:=gethost(getaddress(form2.ListBox1.Items[i]));
 try
   datasmtp.DataModule1.smtp.Connect;
 except
 end;
   if datasmtp.datamodule1.smtp.connected then
   datasmtp.DataModule1.smtp.SendMail;
 end;
end;
end;
  5. 记录邮件发送状态并保存日志

  使用TNMSMTP 的各种事件( 如,OnConnect、OnConnectFailed、OnFailure、OnHostResolved 等等) 记录邮件发送状态。

  实现过程请参见下面的代码:

procedure TDataModule1.smtpConnect(Sender: TObject);
var
 s:string;
begin
  s:=timetostr(time) +‘正在连接' +
  smtp.Post Message.ToAddress.Strings[0] +
  ‘的服务器,请稍候!';
  form3.Label1.Caption:=s;
  form3.Memo1.Lines.Add(s);
end;

procedure TDataModule1.smtpConnectionFailed
 (Sender: TObject);
var
 s:string;
begin
 s:=timetostr(time) +‘连接' +
 smtp.Post Message.ToAddress.strings[0] +
 ‘的服务器失败!';
 form3.Label1.Caption:=s;
 form3.Memo1.lines.add(s);
end;

procedure TDataModule1.smtpSendStart
  (Sender: TObject);
var
 s:string;
begin
 s:=timetostr(time) +‘开始发送给'
 +smtp.postmessage.ToAddress.strings[0]
 +‘的邮件!';
 form3.Label1.Caption:=s;
 form3.memo1.lines.add(s);
end;

procedure TDataModule1.smtpSuccess
  (Sender: TObject);
var
  s:string;
begin
 s:=timetostr(time) +‘成功发送给' +
  smtp.postmessage.ToAddress.strings[0] +
 ‘的邮件!';
 form3.Label1.Caption:=s;
 form3.memo1.Lines.add(s);
end;

procedure TDataModule1.smtpInvalidHost
  (var Handled: Boolean);
var
 s:string;
begin
 s:=timetostr(time) +‘: 发送邮件给' +
 smtp.PostMessage.ToAddress.strings[0] +
'时,服务器' +smtp.Host +‘无效!';
 form3.Label1.Caption:=s;
 form3.memo1.lines.add(s);
end;

procedure TDataModule1.smtpFailure
  (Sender: TObject);
var
 s:string;
begin
 s:=timetostr(time) +‘: 给' +
 smtp.PostMessage.ToAddress.strings[0] +
 ‘的邮件发送失败!';
 form3.Label1.Caption:=s;
 form3.memo1.Lines.add(s);
end;

procedure TDataModule1.smtpHostResolved
 (Sender: TComponent);
var
 s:string;
begin
 s:=timetostr(time) +‘: 已经解析出'
 +smtp.PostMessage.ToAddress.strings[0] +
 ‘的邮件服务器地址!';
 form3.Label1.Caption:=s;
 form3.memo1.Lines.add(s);
end;
  保存日志的代码如下:
procedure TForm3.Button1Click(Sender: TObject);
begin
datasmtp.DataModule1.SaveDialog
1.FileName:=datetostr(date) +‘邮件日志.txt';
  if datasmtp.DataModule1.SaveDialog1.Execute then
memo1.Lines.SaveToFile
(datasmtp.DataModule1.SaveDialog1.FileName);
end;
  使用TNMSMTP 组件我们可以方便地实现批量发送电子邮件的功能,而如果将其组件使用在NSAPI 中,加上高度的密码控制,我们就可以实现一个像在线邮局263 一样的提供邮件服务的网站了。除了发送纯文本的邮件之外,TNMSMTP 组件还可以通过TpostMessage 中的Attachments 属性粘贴附件,从而实现其他格式的文件发送。在本文的源代码中已经加上了发送附件的功能,请参考源程序。另外,本文中的SMTP 服务器是smtp. 加上服务器主机名,如huangjiansword@263.net 的服务器就是smtp.263.net,如果您的smtp 服务器不是这种规则,请修改源程序。

  本程序使用Delphi5.0,在Windows98 中文版下调试通过。