You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi Indy 10邮件PDF附件无法打开,MMS正常问题求助

问题分析与解决:邮件附件混入正文导致PDF无法打开

问题现象

将PDF文件流Base64编码为TStringStream后,附加到MMS可正常使用,但邮件附件无法打开;对附件内容Base64解码后发现,附件中混入了邮件正文内容,这是导致PDF损坏的直接原因。

核心问题代码分析

1. 邮件正文重复赋值破坏多部分结构

在SendEmail函数中,使用TIdMessageBuilder构建邮件并调用FillMessage后,又手动执行IdMessage.Body.Text := Body,这会破坏multipart/mixed类型的邮件结构,导致正文内容被错误注入到附件流中。

2. Base64双重编码

原始代码提前对PDF流做了Base64编码,但Indy的TIdMessageBuilder在添加附件时会自动对二进制内容进行Base64编码,最终附件是双重编码的内容。MMS可直接解析Base64编码内容,所以正常;但邮件客户端解码后得到的是原始Base64字符串而非PDF字节,自然无法打开。

修复方案

步骤1:传递原始PDF字节流,避免提前Base64编码

修改CreateReportPdf函数,直接返回原始PDF字节流(使用TMemoryStream),不再提前做Base64编码:

function CreateReportPdf(QuickRep: TQuickRep; var ReportPdf: TMemoryStream; const PrintNumber, ReportName: string; const IsReprint: Boolean): Boolean;
var
  aPdf: TQRPDFDocumentFilter;
  tmpPath, tmpFileName: string;
  fs: TFileStream;
begin
  Result := False;
  if (not Assigned(QuickRep)) then
    Exit;

  tmpPath := GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) + '\Temp\';
  tmpFileName := CreateTmpFileName(tmpPath, Format('%s_%s', [ReportName, PrintNumber]), '.pdf');

  aPdf := TQRPDFDocumentFilter.Create(tmpPath + tmpFileName);
  fs := nil;

  try
    try
      aPdf.CompressionOn := True;
      aPdf.PageLength := 11;
      QuickRep.ExportToFilter(aPdf);

      fs := TFileStream.Create(tmpPath + tmpFileName, fmOpenRead);
      ReportPdf.Clear;
      ReportPdf.CopyFrom(fs, fs.Size);
      ReportPdf.Position := 0;

      Result := True;
    except
      ShowMessage('Failed to create report PDF.');
      Result := False;
    end;
  finally
    aPdf.Free;
    fs.Free;
  end;
end;

步骤2:修改邮件发送函数,移除重复的正文赋值

修复SendEmail函数,让TIdMessageBuilder全权处理正文和附件,不再手动修改IdMessage.Body:

procedure SendEmail(Host, From, Recipients, Subject, Body, CC, BCC, ReplyTo: string; Port: Integer; IsBodyHtml: Boolean; Attachments: TStrings);
var
  IdSMTP: TIdSMTP;
  IdMessage: TIdMessage;
  builder: TIdCustomMessageBuilder;
  s: Integer;
begin
  IdSMTP := TIdSMTP.Create(nil);
  IdMessage := TIdMessage.Create(nil);

  try
    if IsBodyHtml then
    begin
      builder := TIdMessageBuilderHtml.Create;
      TIdMessageBuilderHtml(builder).Html.Text := Body;
    end
    else
    begin
      builder := TIdMessageBuilderPlain.Create;
      TIdMessageBuilderPlain(builder).PlainText.Text := Body;
    end;

    try
      if (Assigned(Attachments)) then
      begin
        for s := 0 to Attachments.Count -1 do
        begin
          if (Attachments.Strings[s] = 'stream/pdf') then
          begin
            builder.Attachments.Add(TMemoryStream(attachments.Objects[s]), 'application/pdf', Format('%s_%s.pdf', [ReportName, PrintNumber]));
          end
          else
            builder.Attachments.Add(attachments.ValueFromIndex[s]);
        end;
      end;

      builder.FillMessage(IdMessage);
    finally
      builder.Free;
    end;

    IdMessage.From.Address := From;
    IdMessage.Recipients.EMailAddresses := Recipients;
    IdMessage.Subject := Subject;
    IdMessage.CCList.EMailAddresses := CC;
    IdMessage.BccList.EMailAddresses := BCC;
    IdMessage.ReplyTo.EMailAddresses := ReplyTo;
      
    try
      IdSMTP.Host := Host;
      IdSMTP.Port := Port;

      IdSMTP.Connect;
      IdSMTP.Send(IdMessage);
      IdSMTP.Disconnect;
    finally
      IdSMTP.Free;
    end;
  finally
    IdMessage.Free;
  end;
end;

步骤3:调整邮件发送调用逻辑

修改SendReportEmail,传递原始TMemoryStream类型的PDF流:

procedure SendReportEmail(ReportPdf: TMemoryStream; const PrintNumber, ReportName, Recipients, MessageText, ReplyTo: string);
var
  Attachment: TStringList;
  Host: string;
  Port: Integer;
  I: Integer;
begin
  if (ReportPdf.Size > 0) then
  begin
    ReportPdf.Position := 0;   
    Attachment := TStringList.Create();

    try
      Attachment.AddObject('stream/pdf', ReportPdf);          

      Host := GetConfigValue(cCFG_EMAIL_HOST).AsString;
      Port := GetConfigValue(cCFG_EMAIL_PORT).AsInteger;
      From := GetConfigValue(cCFG_EMAIL_FROM).AsString;

      Lib.SendEmail(Host, From, Recipients, ReportName + '_' + PrintNumber,
        MessageText, '', '', ReplyTo, Port, False, Attachment);
    finally
      for I := Attachment.Count -1 downto 0 do
        Attachment.Objects[I].Free;
      Attachment.Free;
    end;
  end;
end;

总结

  • 避免手动修改TIdMessage.Body,完全依赖TIdMessageBuilder构建邮件结构,防止内容混入附件。
  • 直接传递原始二进制流给邮件附件,由Indy自动处理Base64编码,避免双重编码问题。

内容的提问来源于stack exchange,提问作者fourwhey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 11:06:00