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
相关产品推荐
相关产品推荐

