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.
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:
you are leaking the SSL
object. TIdIMAP4
does not take ownership of it.
you are not creating any TIdMessage
objects for TIdIMAP4.Retrieve()
to fill. Allocating an array of objects does not create the objects themselves, you need to create them yourself.
TIdIMAP4.(UID)RetrieveMailBoxSize()
retrieves the total byte size of all emails in the mailbox. To get the number of messages in the mailbox, use TIdIMAP4.MailBox.TotalMsgs
instead, which SelectMailBox()
populates.
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.