Subject: [imapext] Fetching short text snippet for mails

From: Timo Sirainen <[email-masked]>
Date: Mon, 1 Dec 2014 22:39:13 -0800

Nowadays many mail clients want to show about 100 chars of the beginning of mail, so there should be a nice and efficient way for clients to request this. I'm now wondering what would be a nice IMAP way of implementing this. Does anyone have some suggestions? There's what I've come up with so far:

a) FETCH n (SNIPPET)
 - Would require writing a new RFC (or just use X-SNIPPET for internal use)

b) FETCH n (ANNOTATION (/vendor/vendor.dovecot.snippet))
 - Would require implementing ANNOTATE, which is a lot of work. But it could also be implemented just minimally for this specific command.
 - Standardizing vendor.dovecot.snippet would probably require new RFC

c) CONVERT n ("text/vnd.dovecot.snippet" ("length" "100")) BINARY[BODY]
 - Would require implementing CONVERT, which is a lot of work. But it could also be implemented just minimally for this specific command.
 - Standardizing vnd.dovecot.snippet would probably require new RFC

d) CONVERT n ("text/plain") BINARY[BODY]<0.100>
 - CONVERT RFC says it's legal to convert entire multiparts, but doesn't say exactly what should be done with them. This is probably a valid use case though.
 - Downside to this compared to others is that this always fetches the first 100 chars, while the others could also be smarter and skip over quoted text and such.

I think I'm leaning towards b)

---

From: Bron Gondwana <[email-masked]>
Date: Tue, 02 Dec 2014 18:04:43 +1100

On phone, excuse short form response.

We do b at Fastmail, because we have annotations in Cyrus. We call it preview. Our snippet is a highlighted search fragment.

The preview is generated by a callout to an external program which adds the preview, a couple of flags calculated from the headers and body structure, and a flag to say what version of the annotator code did it. This is why all Fastmail messages have a $X-ME-ANNOT-2 flag right now!

The downside with b is standard preview name. Of we can standardize /preview or /htmlpreview or something then I am all for it.

Bron.

On Tue, Dec 2, 2014, at 05:39 PM, Timo Sirainen wrote:
> _______________________________________________
> imapext mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/imapext

-- 
  Bron Gondwana
  [email-masked]

---

From: Dave Cridland <[email-masked]>
Date: Tue, 2 Dec 2014 10:37:37 +0000

On 2 December 2014 at 06:39, Timo Sirainen <[email-masked]> wrote:

> Nowadays many mail clients want to show about 100 chars of the beginning
> of mail, so there should be a nice and efficient way for clients to request
> this. I'm now wondering what would be a nice IMAP way of implementing this.
> Does anyone have some suggestions? There's what I've come up with so far:
>
> a) FETCH n (SNIPPET)
>  - Would require implementing ANNOTATE, which is a lot of work. But it
> could also be implemented just minimally for this specific command.

One of these two.

I also wonder about making this more generic, as a THUMBNAIL, since
thumbnailing/snippeting seems a fairly generic action on any body part,
leaf or not. There's part of me wondering if it's worthwhile having this as
a BODYSTRUCTURE extension, since having this info at that stage seems more
useful.

It'd need an ENABLE, and probably mandatory BINARY, but it'd allow things
like thumbnailing image attachments, showing an image preview of a PDF, or
whatever.

I admit this is vastly more complex than 100 chars of text, though that's
not as simple as a substring anyway.

Dave.

---

From: Jan Kundrát <[email-masked]>
Date: Tue, 02 Dec 2014 12:37:03 +0100

On Tuesday, 2 December 2014 11:37:37 CEST, Dave Cridland wrote:
> I also wonder about making this more generic, as a THUMBNAIL

That's exactly what CONVERT is doing. There's probably a reason nobody 
implements CONVERT, so I suggest not to make this proposal any more 
generic.

With kind regards,
Jan

-- 
Trojitá, a fast Qt IMAP e-mail client -- http://trojita.flaska.net/

---

From: Alexey Melnikov <[email-masked]>
Date: Tue, 2 Dec 2014 12:30:08 +0000

>>  - Standardizing vendor.dovecot.snippet would probably require new RFC

What is snippet? Is it a piece of text or is it an image? This looks like CONVERT.

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Tue, 2 Dec 2014 13:32:47 +0100

> What is snippet? Is it a piece of text or is it an image? This 

It's a piece of text. You've seen it: Modern mobile clients display 
30-200-byte extracts of the message even before you tap the message to read 
it.

The algorithm to extract the proper text is a bit like BASESUBJECT in 
spirit, but can be rather complex if one wants good results.

Arnt

---

From: Dave Cridland <[email-masked]>
Date: Tue, 2 Dec 2014 13:35:35 +0000

> What is snippet? Is it a piece of text or is it an image? This looks like
Yeah, well, everything looks like CONVERT or ANNOTATE. I think that's the
problem with CONVERT and ANNOTATE - they're just so wide-ranging and
generic.

This is an extract or smaller version for a specific purpose (preview), and
if having a specific, standalone mechanism for it means more people will
implement it, that's good.

Also, as Arnt implies, the quality and representativeness (yes, I know) of
the thumbnail/preview/snippet can vary between implementations, and becomes
a differentiating factor in user experience. Differentiating factors that
are comfortingly well within a simple wire protocol, but are clearly
visible to the end-user, feel like a fine thing to me.

Dave.

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Tue, 2 Dec 2014 16:22:56 +0000

IMO, the extensions people implement are the ones that are readily described over coffee. Something that builds on annotate or convert is not, something that adds two lines to the ABNF may be.

That said, I cannot muster much enthusiasm for this proposal. Or against.

Army

---

From: Cyrus Daboo <[email-masked]>
Date: Tue, 02 Dec 2014 11:37:31 -0500

Hi Timo,

--On December 1, 2014 at 10:39:13 PM -0800 Timo Sirainen <[email-masked]> wrote:

> of mail, so there should be a nice and efficient way for clients to
> request this. I'm now wondering what would be a nice IMAP way of
> implementing this. Does anyone have some suggestions? There's what I've
> come up with so far:

Efficient as in eliminating the current extra round trip of doing a FETCH 
BODY.PEEK[<section>]<<partial>>? If so, then I think "SNIPPET" should be an 
alias for BODY.PEEK[the first significant text section]<<partial>>. i.e., 
the server does not do anything more than determine the "the first 
significant text section". The client still gets to use <<partial>> as it 
could today.

-- 
Cyrus Daboo

---

From: Alexey Melnikov <[email-masked]>
Date: Tue, 02 Dec 2014 16:55:54 +0000

Hi Cyrus,

On 02/12/2014 16:37, Cyrus Daboo wrote:
> Efficient as in eliminating the current extra round trip of doing a 
> FETCH BODY.PEEK[<section>]<<partial>>? If so, then I think "SNIPPET" 
> should be an alias for BODY.PEEK[the first significant text 
> section]<<partial>>. i.e., the server does not do anything more than 
> determine the "the first significant text section". The client still 
> gets to use <<partial>> as it could today.
I thought so too. That is why I've asked the question what exactly is 
"snippet". Unless "snippet" needs to convert from text/html to 
text/plain (or vice versa), etc.

---

From: Cyrus Daboo <[email-masked]>
Date: Tue, 02 Dec 2014 12:04:37 -0500

Hi Alexey,

--On December 2, 2014 at 4:55:54 PM +0000 Alexey Melnikov 
<[email-masked]> wrote:

> "snippet". Unless "snippet" needs to convert from text/html to text/plain
> (or vice versa), etc.

Of course there is a lot that a client might do itself to "extract" the 
snippet: convert HTML to plain text (i.e., get rid of all the <..> tags, 
scripts etc, add suitable line breaks for <h> tags), strip quoted text from 
a reply or "quote headers" from a forward.

The thing is each client probably has its own heuristics as to how to 
"extract" a snippet and I am not sure we could agree on a single 
server-side approach that would do.

-- 
Cyrus Daboo

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Tue, 2 Dec 2014 18:06:20 +0100

On Tuesday, December 2, 2014 5:37:31 PM CEST, Cyrus Daboo wrote:
> Efficient as in eliminating the current extra round trip of 
> doing a FETCH BODY.PEEK[<section>]<<partial>>? If so, then I 
> think "SNIPPET" should be an alias for BODY.PEEK[the first 
> significant text section]<<partial>>. i.e., the server does not 
> do anything more than determine the "the first significant text 
> section". The client still gets to use <<partial>> as it could 
> today.

This I like. Simple for the server, simple for the client, a clear win for 
the client. The only disadvantage is that the client still has to do the 
heavy text extraction. But it needs that code anyway if it is to work with 
unextended servers.

Arnt

---

From: Dave Cridland <[email-masked]>
Date: Tue, 2 Dec 2014 17:08:29 +0000

On 2 December 2014 at 17:04, Cyrus Daboo <[email-masked]> wrote:

> Hi Alexey,
>
> --On December 2, 2014 at 4:55:54 PM +0000 Alexey Melnikov <
> [email-masked]> wrote:
>

Yes, I entirely agree. Which is why I suspect that  trying to define a
"single server-side approach" isn't what would add the most value - you
want to define a simple protocol that returns some magic text that acts as
a snippet or preview.

Dave.

---

From: Cyrus Daboo <[email-masked]>
Date: Tue, 02 Dec 2014 12:27:21 -0500

Hi Arnt,

--On December 2, 2014 at 6:06:20 PM +0100 Arnt Gulbrandsen 
<[email-masked]> wrote:

> This I like. Simple for the server, simple for the client, a clear win
> for the client. The only disadvantage is that the client still has to do
> the heavy text extraction. But it needs that code anyway if it is to work
> with unextended servers.

The problem is I am not sure how well this works with HTML where there may 
be a lot of markup prior to any real text being present. That will force 
clients to request a larger enough number of octets in <<partial>> so that 
they have a reasonable chance of extracting "real" text. But then the size 
of the response gets a lot larger and thus it becomes less efficient 
overall - defeating the whole purpose of doing that.

Do we have a summary of client behaviors that shows the set of FETCH 
requests clients currently do to populate their message lists? I think it 
would be useful to see exactly what all the clients do - in particular for 
cases where there are a large number of new messages (e.g., loading a 
mailbox for the first time) vs just a few new messages (sync'ing new mail). 
Perhaps seeing that will clarify exactly what is inefficient and could be 
improved.

-- 
Cyrus Daboo

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Tue, 2 Dec 2014 18:49:11 +0100

Does it matter?

As I see it, your proposal aims to solve the download problem. (Which is a 
prioblem. If it takes several roundtrips from when a mobile client has put 
up a notification until it's ready to ship a snippet, that's often >1s, 
painfully long on a mobile.) You don't solve the problem of extracting the 
right text, but rather presume that the client has logic to solve that.

Instead of a dubious solution to two problems, you supply a good solution 
to one problem. Good, say I.

Arnt

---

From: Jan Kundrát <[email-masked]>
Date: Tue, 02 Dec 2014 19:08:49 +0100

On Tuesday, 2 December 2014 17:37:31 CEST, Cyrus Daboo wrote:
> doing a FETCH BODY.PEEK[<section>]<<partial>>?

I don't think a roundtrip would be neccessary.

Sure, the client doesn't know whether it's a single-part text/plain, or if 
it's a multipart/mixed with plaintext and HTML, or whether it's something 
yet different. However, there's principally no problem in issuing a blind 
BODY.PEEK[1] which will "get the right thing" most of the time. One can 
play it extra-safe and ask for a BODY.PEEK[1.1] as well, these are also 
pretty popular and the worst case is wasting a tiny amount of bandwidth. 
It's OK to request a non-existing part in FETCH anyway.

> If so, then I think "SNIPPET" should be an alias for BODY.PEEK[the first 

So that proposal would be just about finding a "first text part", and maybe 
returning a small, impementation-defined subset of that. As a client 
author, I don't think that such an extension would provide much added value 
to my users. I can already get "99%" of this with no extensions today.

What I would like much more is something "smarter", even if with an 
implementation-defined exact behavior. Finding a textual part is not 
particularly compelling. It should strip away quotations (and do it 
smartly, ignoring the leading "Hi John," or "On ABC, XYZ wrote" prefixes), 
kill the signature, notice that it's a meeting invitation from Exchange and 
display the subject/note along with time and location, etc.

Of course this cannot possibly be specified by an RFC. What I'm arguing 
against is specifying this extension to only extract a chunk of bytes from 
a first textual part.

An extension which I would like to use would use loose wording such as 
"return a textual preview of the e-mail, using implementation defined 
heuristics for determining the content of the response". The RFC can then 
go ahead and suggest what the typical steps to be performed are.

It would be nice to also have a command which returns back the supported 
features of this convertor/extractor. One could go as far as to maintain a 
IANA list of the capabilities, but that is probably an overkill :). I can 
certainly see a couple of use cases for that, though.

With kind regards,
Jan

---

From: Bron Gondwana <[email-masked]>
Date: Wed, 03 Dec 2014 08:07:52 +1100

On Wed, Dec 3, 2014, at 04:49 AM, Arnt Gulbrandsen wrote:
> Does it matter?
>
> As I see it, your proposal aims to solve the download problem. (Which
> is a prioblem. If it takes several roundtrips from when a mobile
> client has put up a notification until it's ready to ship a snippet,
> that's often >1s, painfully long on a mobile.) You don't solve the
> problem of extracting the right text, but rather presume that the
> client has logic to solve that.
>
> Instead of a dubious solution to two problems, you supply a good
> solution to one problem. Good, say I.

As someone who's done both ends of this - your solution isn't enough.
We now calculate an ANNOTATION on the server precisely because it's a
pain to fetch over IMAP.  I'm going to paste our "find the body part to
get preview from" code, so you can see how much complexity went into the
client to get efficient previews from the IMAP server.  And note, you
have to repeat this every time you lose your local cache.

There's plenty around this, but here's the preview and "has attachments"
finding bit.  Notice how it has to batch fetches by which bodystructure part
it wants to fetch the preview from:

    # Get bodystructure for messages we need bodystructure for {{{

    if (my @NeedBSUids = grep { $NeedMoreUids{$_}->{NeedBS} } keys %NeedMoreUids) {
      @NeedBSUids = sort { $a <=> $b } @NeedBSUids;

      # Hold UIDs that need each appropriate flag stored back
      my (@StoreHasAttUids, @StoreHasNoAttUids);

      my $IMAPBS = $Mailbox->fetch(\@NeedBSUids, "(bodystructure)")
        || die "IMAP fetch failed - $@";

      foreach my $Uid (@NeedBSUids) {

        # Get local references to data
        my $Row = $Uid2RowMap{$Uid};
        my $BS = ($IMAPBS->{$Uid} && $IMAPBS->{$Uid}->{bodystructure}) || next;

        # Store back 'hasatt' or 'hasnoatt' flag
        if ($NeedMoreUids{$Uid}->{NeedAttStore}) {

          # Quick heuristic to work out if has attachment
          my $HasAtt = $Self->_HasAttachment($BS);

          # Add id to appropriate list
          if ($HasAtt) {
            push @StoreHasAttUids, $Uid;
          } else {
            push @StoreHasNoAttUids, $Uid;
          }

          # Save new information back into row data
          $Row->{HasAtt} = $HasAtt;
        }

        # Fix 8-bit headers
        if ($NeedMoreUids{$Uid}->{NeedCharset}) {
          my $CharSet = lc($BS->{'Content-Type'}->{charset});

          # If no charset, or we're told it's us-ascii, (but we got here because there
          #  was 8-bit data in the header!), then assume users default charset
          $CharSet = $Self->{FallbackCharset} || 'iso-8859-1'
            if !$CharSet || $CharSet eq 'ascii' || $CharSet eq 'us-ascii';

          # For each 8-bit header, decode it with the charset we now think it's in
          my $Row = $Uid2RowMap{$Uid};
          for (grep { defined($_) && !is_utf8($_) && /[^\x20-\x7f]/ } @$Row{qw(To From Subject)}) {
            eval { $_ = decode($CharSet, $_); };
            # We decoded to utf-8, but there's still control chars? Strip them.
            s/[\x00-\x1f]//g if is_utf8($_) && /[\x00-\x1f]/;
          }
        }

        # Save copy of BS for body part below
        $Uid2BSMap{$Uid} = $BS;
      }

      # Save hasatt/hasnoatt flags back
      $Mailbox->store(\@StoreHasAttUids, '+flags.silent', "(hasatt)")
        if @StoreHasAttUids;
      $Mailbox->store(\@StoreHasNoAttUids, '+flags.silent', "(hasnoatt)")
        if @StoreHasNoAttUids;
    }

    # }}}

    # Get preview text for messages we need preview text for {{{

    if (my @NeedBodyUids = grep { $NeedMoreUids{$_}->{NeedPeek} } keys %NeedMoreUids) {
      @NeedBodyUids = sort { $a <=> $b } @NeedBodyUids;

      # Need to fetch by partnum. To fetch multiple at once, map partnum -> uids
      my (%PartNumToUidMap, %UidToPartInfoMap);
      foreach my $Uid (@NeedBodyUids) {

        # Use find_message to find the body part for this message
        #  (we stored the actual bodystructure for the mesage into
        #  %NeedBSUids above)
        my $Parts = find_message($Uid2BSMap{$Uid});
        my ($Part, $Type) = ($Parts->{text}, 'text');

        # If no text part, but an HTML part, use that
        if (!$Part && exists $Parts->{html}) {
          ($Part, $Type) = ($Parts->{html}, 'html');
        } elsif ($Part) {
          $Type = 'rtf' if lc $Part->{'MIME-Type'} eq 'text/enriched';
        }

        my $Partnum = $Part->{'IMAP-Partnum'} || '';

        push @{$PartNumToUidMap{$Partnum}}, $Uid;
        $UidToPartInfoMap{$Uid} = [ $Part, $Type ];
      }

      # Now iterate by partnums
      foreach my $Partnum (keys %PartNumToUidMap) {
        my @Uids = @{$PartNumToUidMap{$Partnum}};

        # Now get the data for all uids with body in this partnum
        my $BodyTexts = $Mailbox->fetch(\@Uids, "binary.peek[$Partnum]<0.2000>");

        # Bad transfer encodings cause error return, but there might be partial data
        if (!$BodyTexts || !ref($BodyTexts)) {
          $BodyTexts = delete $Mailbox->{PartialResp};
        }

        if (!$BodyTexts || !ref($BodyTexts)) {
          warn "fetch binary for part=$Partnum for uids=@Uids for $Self->{UserId} in $FolderDet->{FolderName} failed: $@";
        }

        # And process each of them
        foreach my $Uid (@Uids) {
          my ($Part, $Type) = @{$UidToPartInfoMap{$Uid}};
          my $BodyText = $BodyTexts->{$Uid} && $BodyTexts->{$Uid}->{binary};
          $BodyText = 'Content unavailable for preview. Probably invalid encoding'
            if !defined $BodyText;

          # Check if we need to decode
          # If any non-ascii chars, we have to decode in some way...
          if ($BodyText =~ /[^\x20-\x7f\r\n]/) {

            # Get and normalise charset
            my $Charset = lc($Part->{'Content-Type'}->{charset});
            $Charset = $Self->{FallbackCharset} || 'iso-8859-1'
              if !$Charset || $Charset eq 'ascii' || $Charset eq 'us-ascii';
            $Charset =~ s/^(iso)[\-_]?(\d+)[\-_](\d+)[\-_]?\w*/$1-$2-$3/i;

            eval { $BodyText = decode($Charset, $BodyText); };
          }

          my @Lines = CleanBodyText($BodyText, $Type);

          # Show the first 3 lines or so
          if ($PeekOutput == 1) {

            $BodyText = substr(join("\n", @Lines[0 .. (@Lines < 3 ? @Lines-1 : 2)]), 0, 1000) . "\n";

          } elsif ($PeekOutput == 2) {
            # Show only 1 line with <CR>'s replaced with \

            # Only first 8 lines and/or 200 chars
            $BodyText = substr(join($Self->{PeekSep} // " \\ ", @Lines[0 .. (@Lines < 8 ? @Lines-1 : 7)]), 0, 200) . "\n";
          } 

          # Store back into row data
          $Uid2RowMap{$Uid}->{BodyPeek} = $BodyText;
          $Uid2RowMap{$Uid}->{BodyType} = $Type;
        }
      }

    }

...

And here's the annotator that runs inside our server now, which calculates image sizes for attached images, a preview, and a bunch of things calculated from headers.

sub MakePreview {
    my ($message, $Parts) = @_;

    my ($Part, $Type) = ($Parts->{text}, 'text');

    # If no text part, but an HTML part, use that
    if (!$Part && exists $Parts->{html}) {
      ($Part, $Type) = ($Parts->{html}, 'html');
    } elsif ($Part) {
      $Type = 'rtf' if lc $Part->{'MIME-Type'} eq 'text/enriched';
    }

    my $Preview = "";
    if ($Part) {
      my $CharSet = lc($Part->{'Content-Type'}->{charset} // '');

      my $Content = $message->read_part_content($Part, MAX_CONTENT);
      $Content = MEDecodeBody($Content, $CharSet);

      my @Lines = clean_body_text($Content, $Type);

      # Want at least 3 lines or 160 characters of preview
      my ($Pos, $Length) = (0, 0);
      while ($Length < 160 && $Pos < @Lines) {
	$Length += length($Lines[$Pos++]) + 1;
      }
      $Pos = 3 if $Pos < 3;
      splice(@Lines, $Pos) if $Pos < @Lines;

      $Preview = join "\n", @Lines;
    }

    return Encode::encode_utf8($Preview);
}

And the client just does this:

    # Get previews for messages we need previews for {{{

    if (my @NeedPeekUids = grep { $NeedMoreUids{$_}->{NeedPeek} } @NeedMoreUids) {

      warn "getting preview for uids=" . join(" ", @NeedPeekUids) if DEBUG;

      my @WantItems = ("annotation", [ "/vendor/messagingengine.com/preview", "value.shared" ]);
      my $IMAPAnnot = $Mailbox->fetch(\@NeedPeekUids, \@WantItems)
        || die "IMAP fetch failed - $@ - items=" . join(" ", @WantItems) . ", uids=" . join(",", @NeedPeekUids);

      my $PeekSep = $Opts{PeekSep} // $Self->{PeekSep} // " \\ ";

      # Now extra data we need
      foreach my $Uid (@NeedPeekUids) {

        my $Row = $Uid2RowMap{$Uid};
	$Row->{BodyPeek} = '';

        # Message can always disappear, handle sanely
        my $MsgAnnot = $IMAPAnnot->{$Uid} // next;
	$MsgAnnot = $MsgAnnot->{annotation} // next;
	$MsgAnnot = $MsgAnnot->{"/vendor/messagingengine.com/preview"}->{"value.shared"} // next;

        # Data is utf-8 octets, \n separated lines
	my @Lines = split /\n/, decode_utf8($MsgAnnot);

        my $BodyText = '';

	# 3 lines
	if ($PeekOutput == 1) {

          splice(@Lines, 3) if @Lines > 3;
	  $BodyText = substr(join("\n", @Lines), 0, 1000) . "\n";

	# 1 line with <CR>'s replaced with \
	} elsif ($PeekOutput == 2) {

	  # Only first 8 lines and/or 130 chars
	  splice(@Lines, 8) if @Lines > 8;
	  $BodyText = substr(join($PeekSep, @Lines), 0, 130);
	}

	# Store back into row data
	$Row->{BodyPeek} = $BodyText;
      }
    }

Significantly simpler to implement on the client, and fewer round trips as well.  Best of all, you calculate it ONCE and store it on the server, rather than having to either cache every single one you ever see, or potentially have to re-calculate it every time you go back to a mailbox you haven't seen for a while.

Bron.

-- 
  Bron Gondwana
  [email-masked]

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Tue, 2 Dec 2014 22:56:49 +0100

That's a really good solution. Very much better than e.g. unextended IMAP, 
and even better than base IMAP + Cyrus' proposal, except for one thing: It 
seems to me to have a baseline-like character rather than an extension-like 
character. I don't see that sensible clients would support stock IMAP and 
use your (otherwise fine) extension if present.

If the goal is to produce something that mobile clients can require and 
use, while dropping support for unextended IMAP, then I'm all for your 
proposal.

Arnt

---

From: Bron Gondwana <[email-masked]>
Date: Wed, 03 Dec 2014 10:35:42 +1100

On Wed, Dec 3, 2014, at 05:08 AM, Jan Kundrát wrote:
> smartly, ignoring the leading "Hi John," or "On ABC, XYZ wrote"
> prefixes), kill the signature, notice that it's a meeting invitation
> from Exchange and display the subject/note along with time and
> location, etc.
>
> Of course this cannot possibly be specified by an RFC. What I'm
> arguing against is specifying this extension to only extract a chunk
> of bytes from a first textual part.

It's like FUZZY search.  Rather than specifying an exact algorithm which
can be identical in every server, we're much better off saying "present
a preview that gives a brief overview of what's in the message" and let
the server calculate it as it sees fit.

> heuristics for determining the content of the response". The RFC can
> then go ahead and suggest what the typical steps to be performed are.
>
> It would be nice to also have a command which returns back the
> supported features of this convertor/extractor. One could go as far
> as to maintain a IANA list of the capabilities, but that is
> probably an overkill :). I can certainly see a couple of use cases
> for that, though.

Yeah, definitely overkill.

The one thing that we have at FastMail above this basic preview which is
pretty nice is a search-snippet version  which takes a search phrase and
returns a couple of parts of text around the matches from that search.

http://i.imgur.com/jf2k6gI.png

This is different from the normal previews:

http://i.imgur.com/EuhmfgR.png

Bron.

-- 
  Bron Gondwana
  [email-masked]

---

From: Alexey Melnikov <[email-masked]>
Date: Wed, 03 Dec 2014 12:33:07 +0000

On 02/12/2014 11:37, Jan Kundrát wrote:
> That's exactly what CONVERT is doing.
It is. And if one doesn't implement complicated conversion parameters, 
supported MIME type discovery, this would be an easy use case:

c001 UID CONVERT 2001 ("TEXT/PLAIN") BINARY[1]<1.100>

> There's probably a reason nobody implements CONVERT, so I suggest not 
> to make this proposal any more generic.

---

From: Paul Smith <[email-masked]>
Date: Wed, 03 Dec 2014 10:42:11 +0000

On 02/12/2014 17:06, Arnt Gulbrandsen wrote:
> for the client. The only disadvantage is that the client still has to 
> do the heavy text extraction. But it needs that code anyway if it is 
> to work with unextended servers.
Agreed

You aren't going to be able to solve the problem perfectly to everyone's 
satisfaction - at least not in an extension which will ever be 
implemented widely.

So, keep it simple, and make things *better* than they are now. Servers 
implementers will include simple extensions, and the client will benefit 
somewhat. No, it may not be perfect, but it's better than now.

You could go a step further and say that SNIPPET should strip HTML 
control code out of any HTML sections (as long as the rules for doing so 
are defined in the RFC, eg tags, text between <style></style> tags, etc)

-

Paul Smith Computer Services
Tel: 01484 855800
Vat No: GB 685 6987 53

Sign up for news & updates at http://www.pscs.co.uk/go/subscribe

---

From: Dave Cridland <[email-masked]>
Date: Thu, 4 Dec 2014 14:04:34 +0000

This is exactly what I think doesn't need specifying. It doesn't matter if
every implementation generates a slightly different snippet, it just
matters that a client can get it in the same way.

Dave.

---

From: Michael M Slusarz <[email-masked]>
Date: Fri, 05 Dec 2014 17:25:38 -0700

Quoting Jan Kundrát <[email-masked]>:

>> FETCH BODY.PEEK[<section>]<<partial>>?
>
> Sure, the client doesn't know whether it's a single-part text/plain,  
> or if it's a multipart/mixed with plaintext and HTML, or whether  
> it's something yet different. However, there's principally no  
> problem in issuing a blind BODY.PEEK[1] which will "get the right  
> thing" most of the time. One can play it extra-safe and ask for a  
> BODY.PEEK[1.1] as well, these are also pretty popular and the worst  
> case is wasting a tiny amount of bandwidth. It's OK to request a  
> non-existing part in FETCH anyway.

We've found this kind of blind-fetching technique isn't very useful,  
at least coming from the perspective of a client that has to handle  
1000's of concurrent users.

If building a mailbox listing, there's a good chance that  
envelop(-ish) data is cached on the server.  Once you start adding  
body fetches to this mailbox listing it becomes more expensive I/O  
wise to generate these responses.

And this doesn't work very well for multipart/alternative e-mails,  
especially since more and more of those messages have a [1] text/plain  
content of either "Go to URL http://example.com/ to view the contents  
of this message" or an automated down-converted representation of the  
HTML part that is difficult to read.

For a 50,000 foot preview of the message content, I have no issue  
relying on a server to figure out how to parse the  
multipart/alternative message and strip out the HTML tags in order to  
provide something useful.

michael

___________________________________
Michael Slusarz [[email-masked]]

---

From: Dave Cridland <[email-masked]>
Date: Sat, 6 Dec 2014 09:50:58 +0000

>> Sure, the client doesn't know whether it's a single-part text/plain, or
if it's a multipart/mixed with plaintext and HTML, or whether it's
something yet different. However, there's principally no problem in issuing
a blind BODY.PEEK[1] which will "get the right thing" most of the time. One
can play it extra-safe and ask for a BODY.PEEK[1.1] as well, these are also
pretty popular and the worst case is wasting a tiny amount of bandwidth.
It's OK to request a non-existing part in FETCH anyway.
>
>
> We've found this kind of blind-fetching technique isn't very useful, at
least coming from the perspective of a client that has to handle 1000's of
concurrent users.
>
> If building a mailbox listing, there's a good chance that envelop(-ish)
data is cached on the server.  Once you start adding body fetches to this
mailbox listing it becomes more expensive I/O wise to generate these
responses.
especially since more and more of those messages have a [1] text/plain
content of either "Go to URL http://example.com/ to view the contents of
this message" or an automated down-converted representation of the HTML
part that is difficult to read.
>
> For a 50,000 foot preview of the message content, I have no issue relying
on a server to figure out how to parse the multipart/alternative message
and strip out the HTML tags in order to provide something useful.

+1 - Also I really don't think every octet of the preview needs specifying
rigidly.

---

From: Jan Kundrát <[email-masked]>
Date: Sat, 06 Dec 2014 11:42:15 +0100

On Saturday, 6 December 2014 01:25:38 CEST, Michael M Slusarz wrote:
> envelop(-ish) data is cached on the server.  Once you start 
> adding body fetches to this mailbox listing it becomes more 
> expensive I/O wise to generate these responses.

If I'm reading this correctly, this means that you won't use this preview 
thing either because it fetches actual body part data. Fair enough, that's 
probably a good optimization, but I do not see how is that relevant to the 
proposed extension which would have a very similar cost. Unless you assume 
servers will add adaptive caching, which they can already do now.

With kind regards,
Jan

---

From: Dave Cridland <[email-masked]>
Date: Sat, 6 Dec 2014 11:25:14 +0000

Except caching based on an arbitrary prefix is harder than caching based on
a known semantic preview. Caching and other optimizations are generally
easier when semantics are well defined.

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Thu, 11 Dec 2014 12:14:34 +0100

> the server calculate it as it sees fit.

Fuzzy is roughly like this:

   if(server supports fuzzy)
      send "fuzzy + " foo
   else
      send "(or subject " + foo + " body " + foo + ")"

Your client uses your snippet extension more like this:

   assert(server supports snippet)
   send "uid fetch snippet"

Quite similar in that it's simple for the client and good for the user, but 
there is a difference between if and assert ;)

Arnt

---

From: Bron Gondwana <[email-masked]>
Date: Thu, 11 Dec 2014 22:20:10 +1100

> there is a difference between if and assert ;)

if (server support snippet)
  send "uid fetch snippet"
else if (use dumb snippets anyway)
  send "uid fetch BODY<0.140>"
else
  don't display snippet field

It's not rocket surgery.

(there's a complex else case too, I pasted it earlier, but the easy is to just not display it, or display the first 140 bytes of the body)

-- 
  Bron Gondwana
  [email-masked]

---

From: Stuart Brandt <[email-masked]>
Date: Thu, 11 Dec 2014 08:41:50 -0500

Is snippet intended to be immutable? A server figuring out how to best 
parse an HTML body part and offer up of the most meaningful snippet for 
a user almost assuredly will result in versioning of the extraction 
algorithm, thereby leading to the potential for a mutable snippet element.

Additionally, a fixed sized snippet seems limiting given the trend of 
using the 'stretch' gesture on mobile devices to expose an increasing 
amount of snippet/message while viewing a message listing.

- Stuart

On 12/5/14 7:25 PM, Michael M Slusarz wrote:
> relying on a server to figure out how to parse the multipart/alternative
> message and strip out the HTML tags in order to provide something useful.

---

From: Paul Smith <[email-masked]>
Date: Thu, 11 Dec 2014 13:48:54 +0000

On 11/12/2014 13:41, Stuart Brandt wrote:
> parse an HTML body part and offer up of the most meaningful snippet 
> for a user almost assuredly will result in versioning of the 
> extraction algorithm, thereby leading to the potential for a mutable 
> snippet element.

Does it matter?

> amount of snippet/message while viewing a message listing.

Surely the point of 'snippet' is to allow the client to get 100+ 
snippets in one FETCH command (like they can get subject, sender, date). 
That has to be quick. If it takes 0.1 seconds per message to 
generate/retrieve the snippet, that's too slow in this situation. Having 
a fixed size snippet will mean that the server can cache/precalculate 
it, making the responses potentially really quick.

Once the user starts interacting with a particular email, then the 
client will probably want to fetch the whole body content, as the user 
is interested in the message. If it takes 0.1 seconds here, it doesn't 
matter.

-

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Thu, 11 Dec 2014 15:17:26 +0100

On Thursday, December 4, 2014 3:04:34 PM CEST, Dave Cridland wrote:
> This is exactly what I think doesn't need specifying. It 
> doesn't matter if every implementation generates a slightly 
> different snippet, it just matters that a client can get it in 
> the same way.

Right. But OTOH the snippet has to be as least as good as the client's 
stock-IMAP fallback. I looked at two now, and both go to some length to 
snip away quoted matter etc (the first 270-odd characters of this messsage) 
and parse HTMML.

Arnt

---

From: Arnt Gulbrandsen <[email-masked]>
Date: Thu, 11 Dec 2014 15:29:56 +0000

Now I worry that I may have tested one twice. Oh well.

Arnt

---

From: Stuart Brandt <[email-masked]>
Date: Thu, 11 Dec 2014 10:47:34 -0500

Hi Paul -

My responses are inline

>> extraction algorithm, thereby leading to the potential for a mutable

Yes. Mutable implies the need for synchronizing snippet changes between 
client and server, which in turn might have implications for 
QRESYNC/CONDSTORE or other synchronization approaches.

> is interested in the message. If it takes 0.1 seconds here, it doesn't

I don't dispute the value of fixed sized snippet for caching. What I'm 
questioning is whether all clients and all servers should use a single 
fixed size indicated by this spec. Whether it be 'relaxed' vs. 'compact' 
list spacing in desktop Web clients, or pinch/stretch gestures performed 
on a tablet's message list, user viewable snippet sizes are something 
that's been changing over the last few years. Given these changes, 
something along the line of...
- server advertising its max snippet size
- client indicating how much of the snippet it wants
...seems to be a bit more flexible than a fixed size.

- Stuart

---

From: Paul Smith <[email-masked]>
Date: Thu, 11 Dec 2014 16:53:58 +0000

On 11/12/2014 15:47, Stuart Brandt wrote:
> Yes. Mutable implies the need for synchronizing snippet changes 
> between client and server, which in turn might have implications for 

But does it matter?

What I mean is, if one day, the SNIPPET gets returned as:

"Hi Paul - My responses are inline On 12/11/14 8:48 AM, Paul Smith wrote: "

and the next day it would have been returned as:

"Hi Paul - My responses are inline Yes. Mutable implies the need for 
synchronizing snippet changes"

does that really matter? Will it cause real problems if the client has 
cached the old snippet and doesn't refresh it?

> fixed size indicated by this spec. Whether it be 'relaxed' vs. 
> 'compact' list spacing in desktop Web clients, or pinch/stretch 
> gestures performed on a tablet's message list, user viewable snippet 
> sizes are something that's been changing over the last few years. 
> Given these changes, something along the line of...
OK, as long as the server can specify its maximum size, and the client 
can't expect to get more than that. In that case, the server can just 
cache the maximum size and return a subset of that if the client wants 
less. If the client can request any size snippet, then the server could 
have to work it out each time.

-

---

From: Stuart Brandt <[email-masked]>
Date: Thu, 11 Dec 2014 12:28:17 -0500

Inline...

> cached the old snippet and doesn't refresh it?

The real problem is an impact to the user experience.

Setting text/plain aside because in multipart/alternative cases the 
text/html part is what's most useful for snippet generation...we see all 
sorts of interesting malformed HTML variants that are expected to be 
handled gracefully. Generation of snippet from such cases can yield some 
pretty useless snippets. When such a case is found and a workaround is 
released, it would be in the user's best interest if the prior ugly 
snippet were "fixed".

When this sort of issue comes up today with client-side snippet 
generation (or HTML rendering for that matter), a new app/client is 
released and in some cases it's auto-updated on the user's device. In 
the end, the user experience is made whole.

---

From: Bron Gondwana <[email-masked]>
Date: Fri, 12 Dec 2014 08:06:10 +1100

> the end, the user experience is made whole.

You know what, messages don't change that often overall.  I would be totally happy with the idea that every time the modseq changes, the snippet is stale and a client should refetch if it cares.  The whole point is that it's

a) small
b) cheap for the server to deliver

So it's going to cost barely anything in bandwidth or server time to resend the snippet for any message that's had a modseq bump.

Bron.

-- 
  Bron Gondwana
  [email-masked]

---

From: Stuart Brandt <[email-masked]>
Date: Thu, 11 Dec 2014 17:05:42 -0500

> So it's going to cost barely anything in bandwidth or server time to resend the snippet for any message that's had a modseq bump.

Sounds good. I'm assuming a modseq change whenever snippet changes would 
be appropriate too?

- Stuart

---

From: Bron Gondwana <[email-masked]>
Date: Fri, 12 Dec 2014 13:46:28 +1100

> Sounds good. I'm assuming a modseq change whenever snippet changes would 

Absolutely.  Right now at FastMail we just use the rfc 5257 annotation /vendor/messagingengine.com/preview, so we get a modseq change "for free" when we change it, it's just part of the data model.

Bron.

-- 
  Bron Gondwana
  [email-masked]

---

From: Jamie Nicolson (倪志明) <[email-masked]>
Date: Tue, 16 Dec 2014 11:39:50 -0800

Just weighing in from Gmail. We have snippets generated by our proprietary
algorithm, the ones we show in the web UI. We could implement proposal a),
returning our snippet as a new FETCH attribute, with the understanding that
snippet content is unspecified and determined by the server. We're not
likely to implement ANNOTATE or CONVERT.

On Mon, Dec 1, 2014 at 10:39 PM, Timo Sirainen <[email-masked]> wrote:
>  - Would require implementing CONVERT, which is a lot of work. But it
>  - CONVERT RFC says it's legal to convert entire multiparts, but doesn't
> say exactly what should be done with them. This is probably a valid use
> case though.
>  - Downside to this compared to others is that this always fetches the
> first 100 chars, while the others could also be smarter and skip over
> quoted text and such.
>