Search code examples
delphiimapindy

How to download a message, store it and recreate it in a different folder?


I'm using Delphi 2006, Indy 10 (ver. 4957), IMAP4.

I would like to download an e-mail message, store it and some weeks later I would like to recreate it in a different folder. (It is sort of archiving and restoring it, so simple moving between folders does not work as I will delete the original message.) I download the message, store it, then make a copy of it with AppendMsg.

It works until that point when I check the target Temp2 folder, where most of the messages contain

This is a multi-part message in MIME format

unit Mail_Test;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  StdCtrls;

type
  TForm1 = class( TForm )
    memLog: TMemo;
    btn1: TButton;
    procedure btn1Click( Sender: TObject );
  private
    procedure Log( LogMsg: string );
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  IdIMAP4,
  IdMessage,
  IdExplicitTLSClientServerBase,
  IdSSLOpenSSL;

{$R *.dfm}

procedure TForm1.btn1Click( Sender: TObject );
var
  IMAPClient: TIdIMAP4;
  UsersFolders: TStringList;
  OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
  res: Boolean;
  i: integer;
  inbox, currUID: string;
  cntMsg: integer;
  msg, msg2: TIdMessage;
  BodyTexts: TStringList;
  flags: TIdMessageFlagsSet;
  fileName_MailSource, TmpFolder: string;
begin

  IMAPClient := TIdIMAP4.Create( nil );
  try
    OpenSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create( nil );
    try
      IMAPClient.Host := 'imap.gmail.com';
      IMAPClient.Port := 993;
      IMAPClient.Username := '[email protected]';
      IMAPClient.Password := '....';

      if Pos( 'gmail.com', IMAPClient.Host ) > 0 then begin
        OpenSSLHandler.SSLOptions.Method := sslvSSLv3;
        IMAPClient.IOHandler := OpenSSLHandler;
        IMAPClient.UseTLS := utUseImplicitTLS;
      end;

      try
        res := IMAPClient.Connect;
        if not res then begin
          Log( '  Unsuccessful connection.' );
          exit;
        end;

      except
        on e: Exception do begin
          Log( '   Unsuccessful connection.' );
          Log( '  (' + Trim( e.Message ) + ')' );
          exit;
        end;
      end;

      try
        UsersFolders := TStringList.Create;
        try
          res := IMAPClient.ListMailBoxes( UsersFolders );
          if not res then begin
            Log( '  ListMailBoxes error.' );
            exit
          end;
        except
          on e: Exception do begin
            Log( '  ListMailBoxes error.' );
            Log( '  (' + Trim( e.Message ) + ')' );
            exit;
          end;

        end;

        Log( 'User folders: ' + IntToStr( UsersFolders.Count ) );
        for i := 0 to UsersFolders.Count - 1 do begin
          Log( '  [' + inttostr( i + 1 ) + '/' + inttostr( UsersFolders.Count ) + '] Folder: "' + UsersFolders[ i ] + '"' );
        end;

        IMAPClient.RetrieveOnSelect := rsDisabled;
        inbox := 'INBOX';
        Log( 'Opening folder "' + inbox + '"...' );
        res := IMAPClient.SelectMailBox( inbox );
        cntMsg := IMAPClient.MailBox.TotalMsgs;
        Log( 'E-mails to read: ' + IntToStr( cntMsg ) );

    //    res := IMAPClient.RetrieveAllEnvelopes( AMsgList );

        msg := TIdMessage.Create( nil );
        msg2 := TIdMessage.Create( nil );
        BodyTexts := TStringList.Create;
        TmpFolder := 'c:\';
        res := IMAPClient.CreateMailBox( 'Temp2' )
        try

          for I := 0 to cntMsg - 1 do begin

            Log( '  [' + inttostr( i + 1 ) + '/' + inttostr( cntMsg ) + '] E-mail...' );

            IMAPClient.GetUID( i + 1, currUID );

            Log( '(Downloading message...)' );
            IMAPClient.UIDRetrieve( currUID, msg );

            fileName_MailSource := TmpFolder + 'Log_Mail_' + currUID + '.eml';
            msg.SaveToFile( fileName_MailSource, false );

            // In the final version I will delete the original message 
            // so I have to recreate it from the archived file

            msg2.LoadFromFile( fileName_MailSource );

            res := IMAPClient.AppendMsg( 'Temp2', msg2, msg2.Headers, [] );
          end;
        finally
          FreeAndNil( msg );
          FreeAndNil( msg2 );
          FreeAndNil( BodyTexts )
        end;

      finally
        IMAPClient.Disconnect;
      end;
    finally
      OpenSSLHandler.Free;
    end;
  finally
    IMAPClient.Free;
  end;
end;

procedure TForm1.Log( LogMsg: string );
begin
  memLog.Lines.Add( LogMsg );
  Application.ProcessMessages;
end;

end.

Solution

  • You are calling the version of AppendMsg() that lets you specify alternative email headers. In just about every situation I can think of, you will never want to do that (I don't even know why TIdIMAP4 exposes that functionality).

    The reason is because AppendMsg() saves the TIdMessage to an internal TStream and then sends the email body from that TStream to the server. If you specify alternative headers, they will be sent as-is and not match the header data that was used to create the email body. Most importantly, the MIME boundary used to separate MIME parts within the email body will not match the boundary specified in the headers that are actually sent to the server, which would account for the symptoms you are seeing. That boundary value is randomly generated by TIdMessage whenever it is encoded, so it is not available in the TIdMessage.Headers property prior to calling AppendMsg().

    So, with that said, I strongly suggest you change your code to set the AAlternativeHeaders parameter of AppendMsg() to nil (or use the overloaded version of AppendMsg() that does not have an AAlternativeHeaders parameter at all) so that AppendMsg() will send the actual headers that TIdMessage itself generates when it is encoded prior to upload:

    res := IMAPClient.AppendMsg( 'Temp2', msg2, nil, [] );
    

    Or:

    res := IMAPClient.AppendMsg( 'Temp2', msg2, [] );