Search code examples
delphipdfstreamindy

Indy. sending Attach pdf by stream


I'm having trouble sending attachments via stream. I use Indy 10.6.2 and Delphi Berlin. The mail consists of html with attached images, plus one or more PDF files inserted directly from the database. I don't get any errors in the process. Mail is sent seamlessly, but attached PDFs are not received.

I look forward to comments

 Msg := TIdMessage.Create(self);
  try
    Msg.ContentType := 'multipart/mixed';
    Msg.From.Name := FromName;
    Msg.From.Address := FromAddress;
    Msg.Priority := mpHigh;
    Msg.Subject := Asunto;

    with TIdMessageBuilderHtml.Create do
      try
        if FMsgTxtPlnPac <> '' then
          PlainText.Text := FMsgTxtPlnPac;

        if FMsgHtmlPac <> '' then
        begin
          Html.Text := FMsgHtmlPac;
          n := 0;
          for s in FMsgHTMLFiles.Split([',']) do
          begin
            n := Succ(n);
            c := 'img' + inttostr(n);
            HTMLFiles.Add(s, c); // cid de imagen en HTML(cid:img1, cid:img2...)
          end;

        end;

        if AttachFiles <> '' then
          for s in AttachFiles.Split([',']) do
            Attachments.Add(s);

        // Attach from DB
        while not dm.qryBlb.Eof do
        begin
          Attach := TIdAttachmentMemory.Create(Msg.MessageParts);
          Attach.ContentType := 'application/pdf';
          Attach.FileName := dm.qryBlb.FieldByName('nombre_archivo').AsString;
          Attach.LoadFromStream(dm.GetDataBlbStrm('DATA_TXT')); // this ok in attach.datastream.size
          Attach.CloseLoadStream;    
          dm.qryBlb.Next;
        end;

        FillMessage(Msg);
      finally
        Free;
      end;

    for s in FMailPac.Split([',']) do
    begin
      EmailAddress := Trim(s);
      if EmailAddress <> '' then
      begin
        with Msg.recipients.Add do
        begin
          Address := EmailAddress;
        end;
      end;
    end;

    for s in MailCC.Split([',']) do
    begin
      EmailAddress := Trim(s);
      if EmailAddress <> '' then
        Msg.CCList.Add.Address := EmailAddress;
    end;

    for s in MailCCO.Split([',']) do
    begin
      EmailAddress := Trim(s);
      if EmailAddress <> '' then
        Msg.BccList.Add.Address := EmailAddress;
    end;

 finally
   SMTP1.Send(Msg);
 end;

Solution

  • TIdMessageBuilderHtml supports adding attachments via streams, as well as via files. However, those streams have to remain alive for the duration that the TIdCustomMessageBuilder.Attachments collection is populated, which is not an option in your case since you are looping through DB records one at a time, thus you would only be able to access 1 DB stream at a time.

    You could create a local array/list of TMemoryStream objects, and then populate the TMessageBuilderHtml with those streams, but you will end up wasting a lot of memory that way since TIdMessageBuilderHtml would make its own copy of the TMemoryStream data. And there is no way to have TIdMessageBuilderHtml just use your TMemoryStream data as-is in a read-only mode (hmm, I wonder if I should add that feature!).

    The reason why your manual TIdAttachmentMemory objects don't work is simply because TIdCustomMessageBuilder.FillMessage() clears the TIdMessage's body before then re-populating it, thus losing your attachments (and various other properties that you are setting manually beforehand).

    You would have to add your DB attachments to the TIdMessage after FillMessage() has done its work first. But, then you risk TIdMessageBuilderHtml not setting up the TIdMessage structure properly since it wouldn't know your DB attachments exist.

    On a side note, you are not using TIdAttachmentMemory correctly anyway. Do not call its CloseLoadStream() method if you have not called its OpenLoadStream() method first. Calling its LoadFromStream() method is enough in this case (or, you can even pass the TStream to TIdAttachmentMemory's constructor). Do note, however, that you are leaking the TStream returned by dm.GetDataBlbStrm().

    So, in this case, you are probably better off simply populating the TIdMessage manually and not use TIdMessageBuilderHtml at all. Or, you could derive a new class from TIdMessageBuilderHtml (or TIdCustomMessageBuilder directly) and override its virtual FillBody() and FillHeaders() methods to take your DB streams into account.