Why is Indy not retrieving some of my mailbodies

delphi, email, indy, pop3

Solution

Not all email content is parsed into the `TIdMessage.MessageParts` collection. MIME parts and attachments are, but other content gets parsed into the `TIdMessage.Body` instead, which you are completely ignoring. You need to look at the `TIdMessage.ContentType` when deciding where to extract content from. Attachments will always be in `TIdMessage.MessageParts`, but text may or may not be, depending on the `TIdMessage.ContentType`.

Problem

I have the following code I use for retrieving mails from a POP3 account. It is working very well most of the time, but from time to time there are some mails where it doesn't retrieve the body. If I test the IdMessage.MessageParts.Count it says that it is 0 - if I use another mailclient to retrieve the mail there is no problem. I have a TIdPOP3 and a TIdMessage component on the form. The connection is OK as there may be some mails that are show OK. I can't figure out any system in which mails are not shown correct and which are not. But there might be one. I use Delphi XE3 and the Indy is version 10.5.9.0 ``` procedure TfrmJsMailCollect.lstMailsClick(Sender: TObject); var MailBody: string; intIndex: integer; begin if (lstMails.Items.Count = 0) or (lstMails.SelCount = 0) then Exit; MailBody := ''; try begin mmoBody.Clear; lstMails.Selected.SubItems.Strings[0]; lstMails.Selected.ImageIndex := 4; conPOP3.Retrieve(lstMails.Selected.Index + 1, IdMessage); for intIndex := 0 To Pred(IdMessage.MessageParts.Count) do begin if (IdMessage.MessageParts.Items[intIndex] is TIdAttachmentFile) then begin // Attachments are skipped end else begin // body text if Pos('text/plain', IdMessage.MessageParts.Items[intIndex].ContentType) <> 0 then begin if TIdText(IdMessage.MessageParts.Items[intIndex]).Body.Text <> '' then begin MailBody := MailBody + TIdText(IdMessage.MessageParts.Items[intIndex]).Body.Text; mmoBody.Lines.Add(MailBody); MemoValidate; end; end; end; end; end; mmoBody.CaretPos.SetLocation(0, 0); Application.ProcessMessages; except Logfile.Error('F_JsMailCollect.lstMailsClick - ' + cxGetResourceString(@sLangPop3ErrorReading)); end; end; ``` With advice from Remy Lebeau and searching the web I ended up with the code below. This does the trick for now, but I would like to improve it so that the memo on my form only shows a nice message that would be readable for everyone - but that may come later. ``` procedure TfrmJsMailCollect.lstMailsClick(Sender: TObject); var MailBody: string; i: integer; ContentType: string; begin if (lstMails.Items.Count = 0) or (lstMails.SelCount = 0) then Exit; try MailBody := ''; mmoBody.Clear; lstMails.Selected.SubItems.Strings[0]; lstMails.Selected.ImageIndex := 4; conPOP3.Retrieve(lstMails.Selected.Index + 1, IdMessage); ContentType := IdMessage.ContentType; case PosInStrArray(ContentType, ['multipart/mixed', 'multipart/alternative', 'text/html', 'text/plain'], False) of 0: begin { multipart/mixed } for i := 0 To Pred(IdMessage.MessageParts.Count) do begin if TIdText(IdMessage.MessageParts.Items[i]).Body.Text <> '' then begin MailBody := MailBody + TIdText(IdMessage.MessageParts.Items[i]).Body.Text; mmoBody.Lines.Add(MailBody); MemoValidate; end; end; end; 1: begin { multipart/alternative } for i := 0 To Pred(IdMessage.MessageParts.Count) do begin if TIdText(IdMessage.MessageParts.Items[i]).Body.Text <> '' then begin MailBody := MailBody + TIdText(IdMessage.MessageParts.Items[i]).Body.Text; mmoBody.Lines.Add(MailBody); MemoValidate; end; end; end; 2: begin { text/html } mmoBody.Lines := IdMessage.Body; MemoValidate; end; 3: begin { text/plain } mmoBody.Lines := IdMessage.Body; MemoValidate; end; else // nothing supported to display... end; mmoBody.CaretPos.SetLocation(0, 0); Application.ProcessMessages; except Logfile.Error('F_JsMailCollect.lstMailsClick - ' + cxGetResourceString(@sLangPop3ErrorReading)); end; end; ```

Original source