5

How can I retrieve a specific e-mail based on a certain text contained in the message ? For example how Gmail search works. If you search for a specific text, which is in the e-mail, then the Gmail will retrieve the message that is associated with the text. Preferably without any looping.

TLama
  • 75,147
  • 17
  • 214
  • 392

1 Answers1

6

You're looking for the SearchMailBox method. Here's a simple example expecting that you have the IMAP client (in this case, the IMAPClient variable of the TIdIMAP4 type) already connected to the Gmail server. For those looking for how to do so, take a look for instance at this post and put this code inside the try..finally block near IMAPClient.Connect and IMAPClient.Disconnect.

var
  // in this example is not shown how to connect to Gmail IMAP server but
  // it's expected that the IMAPClient object is already connected there
  IMAPClient: TIdIMAP4;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  MsgObject: TIdMessage;
  SearchInfo: array of TIdIMAP4SearchRec;
begin
  // if the mailbox selection succeed, then...
  if IMAPClient.SelectMailBox('INBOX') then
  begin
    // set length of the search criteria to 1
    SetLength(SearchInfo, 1);
    // the SearchKey set to skBody means to search only in message body texts
    // for more options and explanation, see comments at the TIdIMAP4SearchKey
    // enumeration in the IdIMAP4.pas unit
    SearchInfo[0].SearchKey := skBody;
    // term you want to search
    SearchInfo[0].Text := 'Search term';

    // if the search in the selected mailbox succeed, then...
    if IMAPClient.SearchMailBox(SearchInfo) then
    begin
      // iterate the search results
      for I := 0 to High(IMAPClient.MailBox.SearchResult) do
      begin
        // make an instance of the message object
        MsgObject := TIdMessage.Create(nil);
        try
          // try to retrieve currently iterated message from search results
          // and if this succeed you can work with the MsgObject
          if IMAPClient.Retrieve(IMAPClient.MailBox.SearchResult[I], 
            MsgObject) then
          begin
            // here you have retrieved message in the MsgObject variable, so
            // let's do what what you need with the >> MsgObject <<
          end;
        finally
          MsgObject.Free;
        end;
      end;
    end;
  end;
end;

Here's the quick implementation of the IMAP search for UTF-8 charset. It uses interposed class due to protected ParseSearchResult method:

type
  TBasicSearchKey = (bskBcc, bskBody, bskCc, bskFrom, bskHeader, bskKeyword,
    bskSubject, bskText, bskTo);
const
  IMAPSearchKeys: array [TBasicSearchKey] of string = ('BCC', 'BODY', 'CC',
    'FROM', 'HEADER', 'KEYWORD', 'SUBJECT', 'TEXT', 'TO');
type
  TIdIMAP4 = class(IdIMAP4.TIdIMAP4)
  public
    function SearchMailBoxUTF8(const ASearchText: string;
      ASearchKey: TBasicSearchKey): Boolean;
  end;

implementation

{ TIdIMAP4 }

function TIdIMAP4.SearchMailBoxUTF8(const ASearchText: string;
  ASearchKey: TBasicSearchKey): Boolean;
var
  SearchText: RawByteString;
begin
  Result := False;
  CheckConnectionState(csSelected);

  SearchText := UTF8Encode(ASearchText);
  SendCmd(Format('SEARCH CHARSET UTF-8 %s {%d}', [IMAPSearchKeys[ASearchKey],
    Length(SearchText)]), ['SEARCH']);
  if LastCmdResult.Code = IMAP_CONT then
    IOHandler.WriteLn(SearchText, TEncoding.UTF8);

  if GetInternalResponse(LastCmdCounter, ['SEARCH'], False) = IMAP_OK then
  begin
    ParseSearchResult(FMailBox, LastCmdResult.Text);
    Result := True;
  end;
end;

And the usage:

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  MsgObject: TIdMessage;
begin
  if IMAPClient.SelectMailBox('INBOX') and
    IMAPClient.SearchMailBoxUTF8('Search term', bskText) then
  begin
    for I := 0 to High(IMAPClient.MailBox.SearchResult) do
    begin
      MsgObject := TIdMessage.Create(nil);
      try
        if IMAPClient.Retrieve(IMAPClient.MailBox.SearchResult[I],
          MsgObject) then
        begin
          // here you have retrieved message in the MsgObject variable, so
          // let's do what what you need with the >> MsgObject <<
        end;
      finally
        MsgObject.Free;
      end;
    end;
  end;
end;
Community
  • 1
  • 1
TLama
  • 75,147
  • 17
  • 214
  • 392
  • Does this return HTML or pure text? Because if you search for something in emails that are pure text it works OK. If they are HTML search won't work. –  Nov 28 '12 at 21:49
  • It depends on target IMAP server implementation of the [`SEARCH`](http://tools.ietf.org/html/rfc1730#section-6.4.4) command, but in this case it doesn't search in text message parts unfortunately. Good point to mention. – TLama Nov 28 '12 at 22:15
  • I think there could be a way to set to return pure html or text in gmail settings. –  Nov 28 '12 at 22:16
  • I don't think so. When you call `SearchMailBox` you just send the command to the server, something like `SEARCH BODY "Search term"` and it's upon server how it takes that and what returns you. I'd surprised if that would be configurable individually. Their search might work different, they can loop all the messages in the blink of an eye since they're onsite, so there might not be used `SEARCH` command. – TLama Nov 28 '12 at 22:40
  • Well i was looking for a more universal solution. Is there other IMAP servers that do allow this? Hotmail/Yahoo etc. Because i can adopt to other service. –  Nov 28 '12 at 22:49
  • Well, the `SEARCH` seems to work for all messages; I've just tested it with the search term containing diacritic and those won't work with the code as it is now. The search term should be encoded somehow. – TLama Nov 29 '12 at 02:53
  • There's also the [`IMAP SEARCH`](https://developers.google.com/google-apps/gmail/imap_extensions#extension_of_the_search_command_x-gm-raw) extension which allows the [`extended search`](http://support.google.com/mail/bin/answer.py?hl=en&answer=7190) just like the web interface does. Remy implemented the Gmail IMAP extensions to Indy few moments ago, so there are new Gmail specific search keys (search key for Gmail search is `skGmailRaw`), but as the `skBody`, the search term must be encoded somehow. I'll update the post if Remy won't be faster by posting own answer. – TLama Nov 29 '12 at 02:58
  • can the message be retireved in MsgObject.Body.Text; ? :) –  Nov 29 '12 at 12:39
  • @Joe, I've added a quick implementation of the UTF-8 search (inspired mainly by [`this post`](http://stackoverflow.com/q/7426661/960757)). – TLama Dec 09 '12 at 06:18
  • FYI, for anyone finding this years later, the `TIdIMAP4.(UID)SearchMailBox()` methods support searching for non-ASCII text. They have an optional `ACharset` parameter which defaults to UTF-8, and also UTF-8 is used internally regardless of `ACharset` if the server claims to support UTF-8 quoted strings. This functionality was added to `TIdIMAP4` way back in late Dec 2012 just a few weeks after TLama posted their "quick implementation" update above. – Remy Lebeau Aug 10 '23 at 17:12