Search code examples
delphistreamemail-attachmentsindydelphi-10-seattle

Sending TMemoryStream as mail attachment


I am exporting a FastReport as PDFFile using TfrxPDFExport. I made a function that returns me a PDF as IStream:

Function TPDFReport.GetAsFile (): IStream;
var
  MStream: TMemoryStream;
Begin
  MStream := TMemoryStream.Create;
  try
    With formReport.Create (NIL) do begin
      frxPDFExport.Stream := MStream;
      frxReport.PrepareReport;
      frxReport.Export(frxPDFExport);
    end;
    Result := TStreamAdapter.Create(MStream) as IStream;
  finally

  end;
End;

Afterwards in a different unit I execute this function, get an IStream and load it into a TMemoryStream:

  PDFOleStream := TOleStream.Create(PDFReport.GetAsFile ());
  PDFMemoryStream.LoadFromStream(PDFOleStream);

Now I got the MemoryStream, but I don't really know the next step. I searched through web and found out that's not possible to send a Stream as a mail attachment with Indy. So I think, it's better to save a TMemoryStream as a temp file and then send it as an attachment. But as soon as I execute

PDFMemoryStream.SaveToFile('c:\\PDFExp.pdf');

I get an access violation. What have I done wrong? Thanks.


Solution

  • You do not need to save the TMemoryStream to a file just to attach it to an email. Indy's TIdMessage component is flexible in the types of data you can attach to it.

    For instance, Indy has a TIdAttachmentMemory class you can use, eg:

    uses
     ..., IdAttachmentMemory;
    
    var
      Attach: TIdAttachmentMemory;
    begin
      ...
    
      Attach := TIdAttachmentMemory.Create(IdMessage1, PDFMemoryStream); // alternatively, use PDFOleStream instead...
      Attach.FileName := 'report.pdf';
      // set other properties as needed...
    
      // send the email...
    
      ...
    end;
    

    Note that TIdAttachmentMemory uses its own internal TMemoryStream and will copy the source TStream's data into that internal stream. If you want to avoid that copy and send your own TStream as-is, you can derive your own class from TIdAttachment for that, eg:

    uses
      Classes, IdAttachment, IdMessageParts, IdGlobal;
    
    type
      TIdAttachmentStream = class(TIdAttachment)
      protected
        FDataStream: TStream;
        FDataStreamBeforeLoadPosition: TIdStreamSize;
      public
        constructor Create(Collection: TIdMessageParts; Stream: TStream); reintroduce;
        property DataStream: TStream read FDataStream;
        function OpenLoadStream: TStream; override;
        procedure CloseLoadStream; override;
        procedure FinishTempStream; override;
        function PrepareTempStream: TStream; override;
      end;
    
    constructor TIdAttachmentStream.Create(Collection: TIdMessageParts; Stream: TStream);
    begin
      inherited Create(Collection);
      FDataStream := Stream;
    end;
    
    procedure TIdAttachmentStream.CloseLoadStream;
    begin
      DataStream.Position := FDataStreamBeforeLoadPosition;
    end;
    
    function TIdAttachmentStream.OpenLoadStream: TStream;
    begin
      FDataStreamBeforeLoadPosition := DataStream.Position;
      DataStream.Position := 0;
      Result := DataStream;
    end;
    
    procedure TIdAttachmentStream.FinishTempStream;
    begin
      DataStream.Position := 0;
    end;
    
    function TIdAttachmentStream.PrepareTempStream: TStream;
    begin
      DataStream.Size := 0;
      Result := DataStream;
    end;
    
    var
      Attach: TIdAttachmentStream;
    begin
      ...
    
      Attach := TIdAttachmentStream.Create(IdMessage1, PDFMemoryStream); // alternatively, use PDFOleStream instead...
      Attach.FileName := 'report.pdf';
      // set other properties as needed...
    
      // send the email...
    
      ...
    end;
    

    Or, you can write a class to send the original IStream directly, eg:

    uses
      Classes, IdAttachment, IdMessageParts, IdGlobal;
    
    type
      TIdAttachmentIStream = class(TIdAttachment)
      protected
        FDataStream: TStream;
        FDataStreamBeforeLoadPosition: TIdStreamSize;
      public
        constructor Create(Collection: TIdMessageParts; Stream: IStream); reintroduce;
        destructor Destroy; override;
        property DataStream: TStream read FDataStream;
        function OpenLoadStream: TStream; override;
        procedure CloseLoadStream; override;
        procedure FinishTempStream; override;
        function PrepareTempStream: TStream; override;
      end;
    
    constructor TIdAttachmentIStream.Create(Collection: TIdMessageParts; Stream: IStream);
    begin
      inherited Create(Collection);
      FDataStream := TOleStream.Create(Stream);
    end;
    
    destructor TIdAttachmentIStream.Destroy;
    begin
      FDataStream.Free;
      inherited;
    end;
    
    procedure TIdAttachmentIStream.CloseLoadStream;
    begin
      DataStream.Position := FDataStreamBeforeLoadPosition;
    end;
    
    function TIdAttachmentIStream.OpenLoadStream: TStream;
    begin
      FDataStreamBeforeLoadPosition := DataStream.Position;
      DataStream.Position := 0;
      Result := DataStream;
    end;
    
    procedure TIdAttachmentIStream.FinishTempStream;
    begin
      DataStream.Position := 0;
    end;
    
    function TIdAttachmentIStream.PrepareTempStream: TStream;
    begin
      DataStream.Size := 0;
      Result := DataStream;
    end;
    
    var
      Attach: TIdAttachmentIStream;
    begin
      ...
    
      Attach := TIdAttachmentIStream.Create(IdMessage1, PDFReport.GetAsFile());
      Attach.FileName := 'report.pdf';
      // set other properties as needed...
    
      // send the email...
    
      ...
    end;