Search code examples
emaildelphiindy10

Delphi Indy 10 pdf attachment is unreadable


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;

Solution

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