Delphi Indy 10 pdf attachment is unreadable

199 Views Asked by At

I am attempting to export a report as a PDF then attach it to an Email and/or MMS. I base64 encode the TFileStream to TStringStream, then attach it to an email but cannot open it. Using the same TStringStream, I attach it to an MMS and it works as expected. The Indy method seems to work with a stream, what am I doing wrong?

Edit: The email attachment and the email message body appear to in the file that's attached which is why it isn't readable. If I base64 decode the contents of what should be the attachment, it works.

Any ideas why this is happening?

Email Attachment
MMS Attachment

ReportPdf := TStringStream.Create;
try
  if (CreateReportPdf(QuickRep1, ReportPdf, QryInfoPrintNumber.AsString, 'REPORT', IsReprint)) then
  begin
    if (Assigned(ReportPdf)) then
    begin
      SendReceiptEmail(ReportPdf, QryInfoPrintNumber.AsString, 'REPORT', QryReportName.AsString, 'Test', QryInfoReplyToEmailAddress.AsString);
      SendReceiptSMS(ReportPdf, QryInfoPrintNumber.AsString, 'REPORT', QryReportName.AsString, 'Test');
    end;
  end;
finally
  ReportPdf.Free;
end;

Here's where the report is exported. This works as expected.

function CreateReportPdf(QuickRep: TQuickRep; var ReportPdf: TStringStream; 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);
      fs.Position := 0;
      TNetEncoding.Base64.Encode(fs, ReportPdf);

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

I've tried decoding the base64 (see below) -- didn't help.

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

    try
      // TNetEncoding.Base64.Decode(ReportPdf, ReportPdfDecoded);
      // Attachment.AddObject('stream/pdf', ReportPdfDecoded);
      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;
      // ReportPdfDecoded.Free;
    end;
  end;
end;

Am I missing something obvious here? Thanks for looking.

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;
    end;

    try
      if (Assigned(Attachments)) then
      begin
        IdMessage.ContentType := 'multipart/mixed';
        for s := 0 to Attachments.Count -1 do
        begin
          if (Attachments.Strings[s] = 'stream/pdf') then
          begin
            builder.PlainTextCharSet := 'utf-8';
            builder.Attachments.Add(TStringStream(attachments.Objects[s]), 'application/pdf');
          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.Body.Text := Body;
    IdMessage.CCList.EMailAddresses := CC;
    IdMessage.BccList.EMailAddresses := BCC;
    IdMessage.ReplyTo.EMailAddresses := ReplyTo;

    if not IsBodyHtml then
      IdMessage.Body.Text := Body;
      
    try
      IdSMTP.Host := Host;
      IdSMTP.Port := Port;

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

There are 1 best solutions below

8
Remy Lebeau On BEST ANSWER

There is no need to manually base64-encode the PDF before emailing it with Indy (if you need base64 for SMS, you should handle that separately at the point where you are sending the SMS). Indy's TIdMessage component can handle the base64 for you, so simply attach the original PDF as-is and set its ContentTransfer property to 'base64'. Since you are storing the PDF into a TStream and then attaching that to an email, you should store just the raw bytes of the PDF (ie, using TMemoryStream or TFileStream), do not store a pre-encoded version of the PDF.

Try something more like this:

ReportPdf := TMemoryStream.Create;
try
  if CreateReportPdf(QuickRep1, ReportPdf, QryInfoPrintNumber.AsString, 'REPORT', IsReprint) then
  begin
    SendReceiptEmail(ReportPdf, QryInfoPrintNumber.AsString, 'REPORT', QryReportName.AsString, 'Test', QryInfoReplyToEmailAddress.AsString);
    ...
  end;
finally
  ReportPdf.Free;
end;
function CreateReportPdf(QuickRep: TQuickRep; ReportPdf: TMemoryStream; const PrintNumber, ReportName: string; const IsReprint: Boolean): Boolean;
var
  aPdf: TQRPDFDocumentFilter;
  tmpPath, tmpFileName: string;
begin
  Result := False;
  if not Assigned(QuickRep) then
    Exit;
    
  tmpPath := GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) + '\Temp\';
  tmpFileName := CreateTmpFileName(tmpPath, Format('%s_%s', [ReportName, PrintNumber]), '.pdf');
    
  try
    aPdf := TQRPDFDocumentFilter.Create(tmpPath + tmpFileName);    
    try
      aPdf.CompressionOn := True;
      aPdf.PageLength := 11;
      QuickRep.ExportToFilter(aPdf);
    
      ReportPdf.LoadFromFile(tmpPath + tmpFileName);
      Result := True;
    finally
      aPdf.Free;
    end;
  except
    ShowMessage('Failed to create report PDF.');
  end;
end;
procedure SendReportEmail(ReportPdf: TMemoryStream; const PrintNumber, ReportName, Recipients, MessageText, ReplyTo: string);
var
  Attachment: TStringList;
  Host, From: string;
  Port: 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
      Attachment.Free;
    end;
  end;
end;
procedure SendEmail(Host, From, Recipients, Subject, Body, CC, BCC, ReplyTo: string; Port: Integer; IsBodyHtml: Boolean; Attachments: TStrings);
var
  IdSMTP: TIdSMTP;
  IdMessage: TIdMessage;
  builder: TIdMessageBuilderHtml;
  attach: TIdMessageBuilderAttachment;
  I: Integer;
begin
  IdMessage := TIdMessage.Create(nil);  
  try
    builder := TIdMessageBuilderHtml.Create;
    try
      builder.PlainTextCharSet := 'utf-8';
      builder.HtmlCharSet := 'utf-8';

      if IsBodyHtml then
        builder.Html.Text := Body
      else
        builder.PlainText.Text := Body;
    
      if Assigned(Attachments) then
      begin
        for I := 0 to Attachments.Count - 1 do
        begin
          if Attachments.Strings[I] = 'stream/pdf' then
          begin
            attach := builder.Attachments.Add(TMemoryStream(attachments.Objects[I]), 'application/pdf');
            attach.ContentTransfer := 'base64';
          else
            attach := builder.Attachments.Add(attachments.ValueFromIndex[I]);

          // optional: set attach.WantedFileName or attach.FileName if desired...
        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;

    IdSMTP := TIdSMTP.Create(nil);
    try
      IdSMTP.Host := Host;
      IdSMTP.Port := Port;
    
      IdSMTP.Connect;
      try
        IdSMTP.Send(IdMessage);
      finally
        IdSMTP.Disconnect;
      end;
    finally
      IdSMTP.Free;
    end;
  finally
    IdMessage.Free;
  end;
end;