delphiimapindydelphi-2010gmail-imap

IMAP4 does not authenticate with Gmail in Delphi 2010


I'm very new to Indy, so apologies if there are some glaring issues here.

I'm trying to retrieve emails out of my Gmail inbox, then save them to an array of TIdMessage objects.

I can't find any documentation detailing how to set it up, or a working example (Indy documentation is broken), and my knowledge of working with emails is basically zero.

procedure TfrmEmail.LoadInbox;
var
  IMAP:TIdIMAP4;
  iLength,i:integer;
  SSL:TIdSSLIOHandlerSocketOpenSSL;
begin
  SSL:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  //Set-Up SSL
  with SSL.SSLOptions do
  begin
    Method:=sslvTLSv1;
    Mode:=sslmClient;
    VerifyMode:=[];
    VerifyDepth:=0;
  end;

  IMAP:=TIdIMAP4.Create(Nil);
  //IMAP settings
  with IMAP do
  begin
    IOHandler:=SSL;
    Host:='imap.gmail.com';
    Port:=993;
    Username:={email@gmail.com};
    Password:={app password};
    UseTLS:=utUseImplicitTLS;
    Connect; //works up until here
    SelectMailBox('INBOX');//error here
  end;

  iLength:=IMAP.RetrieveMailBoxSize;
  ShowMessage(inttostr(iLength));
  SetLength(ArrInboxMessages,iLength);
  for i := 1 to iLength do
  begin
    IMAP.Retrieve(i,ArrInboxMessages[i]);
  end;
  IMAP.Disconnect;
  IMAP.Free;
end;

My code works until the Connect part, but as soon as I try to select an inbox I get an exception: EIdConnectionStateError.

My connection state is unauthenticated, and I think this is because I'm not using SASL Authentication.

Another issue might be that I'm using TLSv1, but this works with SMTP and I can't update to the latest version on Delphi 2010.

If anybody could identify the problem and provide a working example/where to find one, that would be very helpful.


Solution

  • Using an App Password with TIdIMAP4.AuthType set to iatUserPass (its default setting) should work just fine, you don't need to use SASL+OAuth (though GMail would prefer that).

    TIdIMAP4.Connect() has an optional AAutoLogin parameter that is True by default, so TIdIMAP4.Login() should be getting called before Connect() exits. If Login() were failing, you would be getting a different exception raised and you would not be able to reach TIdIMAP4.SelectMailBox() to begin with.

    The EIdConnectionStateError exception you are getting means the TIdIMAP4.ConnectionState is not csAuthenticated when you are calling SelectMailBox(). The ConnectionState is set to csNonAuthenticated at the beginning of Connect() and Login(), and then Login() updates it to csAuthenticated if authentication is successful. So, your error should not be possible unless Login() were being bypassed, ie if AAutoLogin=False.

    If needed, you could try calling Login() explicitly after Connect() and before SelectMailBox(), eg:

    Connect;
    if ConnectionState <> csAuthenticated then
      Login;
    SelectMailBox('INBOX');
    

    Alternatively:

    Connect(False);
    Login;
    SelectMailBox('INBOX');
    

    That being said, once you get past the Login issue, there are 2 other problems with your code:

    However, you are manually replicating a feature that TIdIMAP4 already provides to you. TIdIMAP4 has public RetrieveAllHeaders() and RetrieveAllMsgs() methods. And if you set TIdIMAP4.RetrieveOnSelect to rsHeaders or rsMessages then SelectMailBox() will automatically download all available email headers/bodies into TIdIMAP4.Mailbox.MessageList for you.


    UPDATE:

    I didn't notice you were using a very old version of Delphi.

    I looked at the source code for TIdIMAP4 in the version of Indy that would have shipped with Delphi 2010 (see here), and I notice that Login() actually did not raise an exception on failure at that time. That issue was fixed 2 years later (see here) and would have likely shipped in Delphi XE2.

    So, in your case, Login() is likely being called by Connect() but authentication is failing and Login() is not raising an exception on that, thus leaving ConnectionState as csNonAuthenticated. That would account for your situation.

    So, you need to double-check your credentials are actually correct. Calling Login() a second time would be redundant, but you can check whether the TIdIMAP4.LastCmdResult.Code is 'OK' or 'PREAUTH' when Connect() exits, eg:

    Connect; // <-- AAutoLogin=True
    if PosInStrArray(LastCmdResult.Code, ['OK', 'PREAUTH']) = -1 then
      RaiseExceptionForLastCmdResult;
    SelectMailBox('INBOX');
    

    Alternatively:

    Connect(False);
    if ConnectionState <> csAuthenticated then
    begin
      Login;
      if ConnectionState <> csAuthenticated then
      //if LastCmdResult.Code <> 'OK' then
        RaiseExceptionForLastCmdResult;
    end;
    SelectMailBox('INBOX');
    

    However, I would suggest upgrading to the latest version of Indy from its GitHub repo to make sure you have all the latest fixes, including the one where Login() raises an exception on failure. Using the latest version, I am able to use TIdIMAP4 to successfully login to my Gmail with an app password and select the Inbox without error.

    You can update Indy even though you are using an old Delphi version, as Indy still supports Delphi 2010. See the Updating Indy documentation in the repo's Wiki.