Search code examples
delphipdfsmtpattachmentindy

TIdSMTP & TIdAttachmentMemory - Email refused by spam filter


I am trying to send an email with a PDF attachment, stored in a BLOB field, using TIdSMTP. For this I am using a TIdAttachmentMemory, but the code as shown results in 'refused by spam filter';

  1. Omitting IdMessage.ContentType := 'multipart/mixed' works but the attachment is not sent (or received?) - as expected.
  2. Leaving this statement and creating the attachment from a file (as in the commented code) it all works fine (i.e. mail correctly received with attachment).

Clearly I am missing something. I am suspecting something in the direction of the attachment not being "closed off" correctly (i.e. left in an incomplete state) or perhaps the incorrect ContentType?

All suggestions welcome. Thanks!

procedure TfrmSendMail.btnSendClick(Sender: TObject);
var
  ms: TMemoryStream;
  Attachment: TIdAttachmentMemory;
  // Attachment: TIdAttachmentFile;
begin
  memStatus.Clear;

  IdSSLIOHandlerSocketOpenSSL.Destination := teHost.Text + ':587';
  IdSSLIOHandlerSocketOpenSSL.Host := teHost.Text;
  // IdSSLIOHandlerSocketOpenSSL.MaxLineAction := maException;
  IdSSLIOHandlerSocketOpenSSL.Port := 587;
  IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method := sslvTLSv1_2;
  IdSSLIOHandlerSocketOpenSSL.SSLOptions.Mode := sslmUnassigned;
  IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyMode := [];
  IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyDepth := 0;

  IdSMTP.Host := teHost.Text;
  IdSMTP.Port := 587;

  IdMessage.From.Address := teFrom.Text;
  IdMessage.Recipients.EMailAddresses := teTo.Text;
  IdMessage.Subject := teSubject.Text;
  IdMessage.Body.Text := memBody.Text;
  IdMessage.Body.Add('Timestamp: ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()));

  IdMessage.ContentType := 'multipart/mixed';

  if not sqlPDFPDF_Incasso.IsNull then
  begin
    ms := TMemoryStream.Create;
    try
      try
        TBlobField(sqlPDF.FieldByName('PDF_Incasso')).SaveToStream(ms);
        ms.Position := 0;
        Attachment := TIdAttachmentMemory.Create(IdMessage.MessageParts, ms);
        Attachment.ContentType := 'application/pdf';
        Attachment.FileName := 'Invoice.pdf';
      except
        on E: Exception do
          messageDlg('Error creating attachment' + #13#10 + E.Message, mtError, [mbOK], 0);
      end;
    finally
      ms.Free;
    end;
  end;

  // if FileExists(beAttachment.Text) then
  // Attachment := TIdAttachmentFile.Create(IdMessage.MessageParts, beAttachment.Text);

  Screen.Cursor := crHourGlass;
  try
    try
      IdSMTP.Connect;
      IdSMTP.Send(IdMessage);
      memStatus.Lines.Insert(0, 'Email sent - OK.');
    except
      on E: Exception do
        memStatus.Lines.Insert(0, 'ERROR: ' + E.Message);
    end;
  finally
    if assigned(Attachment) then
      Attachment.Free;
    if IdSMTP.Connected then
      IdSMTP.Disconnect(true);
    Screen.Cursor := crDefault;
  end;
end;

Solution

  • You are not populating the TIdMessage correctly (see this blog article for details - your use-case would fall under the "HTML and non-related attachments and no plain-text" section, but replacing HTML with Plain-Text).

    In a nutshell, if you include the attachment, setting the TIdMessage.ContentType to 'multipart/mixed' is fine, but you need to put the body text into a TIdText object in the TIdMessage.MessageParts instead of in the TIdMessage.Body. And if you don't include the attachment, using the TIdMessage.Body is fine, but you need to set the TIdMessage.ContentType to 'text/plain' instead.

    Try this:

    procedure TfrmSendMail.btnSendClick(Sender: TObject);
    var
      Text: TIdText;
      Attachment: TIdAttachmentMemory;
      Strm: TStream;
    begin
      memStatus.Clear;
    
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method := sslvTLSv1_2;
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.Mode := sslmClient;
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyMode := [];
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyDepth := 0;
    
      IdSMTP.Host := teHost.Text;
      IdSMTP.Port := 587;
    
      try
        IdMessage.Clear;
    
        IdMessage.From.Address := teFrom.Text;
        IdMessage.Recipients.EMailAddresses := teTo.Text;
        IdMessage.Subject := teSubject.Text;
    
        //if FileExists(beAttachment.Text) then
        if not sqlPDFPDF_Incasso.IsNull then
        begin
          IdMessage.ContentType := 'multipart/mixed';
    
          Text := TIdText.Create(IdMessage.MessageParts, nil);
          Text.Body.Text := memBody.Text;
          Text.Body.Add('Timestamp: ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()));
          Text.ContextType := 'text/plain';
    
          //Attachment := TIdAttachmentFile.Create(IdMessage.MessageParts, beAttachment.Text);
          Attachment := TIdAttachmentMemory.Create(IdMessage.MessageParts);
          Attachment.ContentType := 'application/pdf';
          Attachment.FileName := 'Invoice.pdf';
          Strm := Attachment.PrepareTempStream;
          try
            TBlobField(sqlPDFPDF_Incasso).SaveToStream(Strm);
          finally
            Attachment.FinishTempStream;
          end;
        end else
        begin
          IdMessage.ContentType := 'text/plain';
    
          IdMessage.Body.Text := memBody.Text;
          IdMessage.Body.Add('Timestamp: ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()));
        end;
    
        Screen.Cursor := crHourGlass;
        try
          IdSMTP.Connect;
          try
            IdSMTP.Send(IdMessage);
          finally
            IdSMTP.Disconnect;
          end;
          memStatus.Lines.Insert(0, 'Email sent - OK.');
        finally
          Screen.Cursor := crDefault;
        end;
      except
        on E: Exception do
          memStatus.Lines.Insert(0, 'ERROR: ' + E.Message);
      end;
    end;
    

    Alternatively, Indy has a TIdMessageBuilderPlain class that can setup the TIdMessage properly for you (see this blog article for details - your use-case would fall under the "Plain-text and HTML and attachments: Non-related attachments only" section):

    uses
      ..., IdMessageBuilder;
    
    procedure TfrmSendMail.btnSendClick(Sender: TObject);
    var
      Strm: TStream;
      Bldr: TIdMessageBuilderPlain;
    begin
      memStatus.Clear;
    
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method := sslvTLSv1_2;
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.Mode := sslmClient;
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyMode := [];
      IdSSLIOHandlerSocketOpenSSL.SSLOptions.VerifyDepth := 0;
    
      IdSMTP.Host := teHost.Text;
      IdSMTP.Port := 587;
    
      try
        IdMessage.Clear;
    
        IdMessage.From.Address := teFrom.Text;
        IdMessage.Recipients.EMailAddresses := teTo.Text;
        IdMessage.Subject := teSubject.Text;
    
        Strm := nil;
        try
          Bldr := TIdMessageBuilderPlain.Create;
          try
            Bldr.PlainText.Text := memBody.Text;
            Bldr.PlainText.Add('Timestamp: ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()));
    
            //if FileExists(beAttachment.Text) then
            if not sqlPDFPDF_Incasso.IsNull then
            begin
              //Bldr.Attachments.Add(beAttachment.Text);
              Strm := sqlPDFPDF_Incasso.DataSet.CreateBlobStream(sqlPDFPDF_Incasso, bmRead);
              Bldr.Attachments.Add(Strm, 'application/pdf').WantedFileName := 'Invoice.pdf';
            end;
    
            Bldr.FillMessage(IdMessage);
          finally
            Bldr.Free;
          end;
        finally
          Strm.Free;
        end;
    
        Screen.Cursor := crHourGlass;
        try
          IdSMTP.Connect;
          try
            IdSMTP.Send(IdMessage);
          finally
            IdSMTP.Disconnect;
          end;
          memStatus.Lines.Insert(0, 'Email sent - OK.');
        finally
          Screen.Cursor := crDefault;
        end;
      except
        on E: Exception do
          memStatus.Lines.Insert(0, 'ERROR: ' + E.Message);
      end;
    end;