Classifying: a line wrap, an HTML entity, an inline tag or a typographic apostrophe hides a phrase #17
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-imap#17
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-imap at
cc09c6381, running messages throughparseand thenclassify(mail.haystack).The rule patterns, and the company and role search in the matching, are written with single spaces, ASCII apostrophes and accented letters. The text they run against is never normalised: it keeps the line breaks of a wrapped body, the doubled spaces left where tags were, undecoded HTML entities, non-breaking spaces and typographic apostrophes. A message that misses for one of these reasons is counted as skipped and, in the default mode, flagged
$Postulo, so it is never looked at again and nothing tells the person.src/postulo_imap/mailbox.py:93-111,plain_text: keeps line breaks;\n{3,}at line 110 does not see CRLF blank lines either.src/postulo_imap/mailbox.py:128-131,_strip_html: replaces each tag with a space, decodes no entity, and keeps the text of<head>and<title>.src/postulo_imap/mailbox.py:78-80,Mail.haystack:f"{self.subject}\n{self.text}".lower()and nothing more.src/postulo_imap/classifying.py:118-125,classify: searches the text as given.src/postulo_imap/matching.py:146-150,_mentions: the same for a company name or a role title of two words.src/postulo_imap/sync.py:183-186andsrc/postulo_imap/sync.py:214-215: no verdict means skipped, and skipped is flagged all the same.EmailMessageor as raw bytes:format=flowedincluded) →None.<p>We <b>regret</b> to inform you</p>→ text'We regret to inform you'→None.We regret to inform you, and the same with U+00A0 characters →None.n'a pas été retenue→None.None; the same sentence with an ASCII apostrophe →rejected(fr).<div>Thank you for</div><div>your application</div>→None.<head>holds<title>Job offer newsletter</title>→offer(en): the title's text is part of the haystack._mentions("we at black\nmesa are", "Black Mesa")and the same with two spaces →False.tests/test_imap.py:242-245).html.unescape, or anhtml.parser.HTMLParserwithconvert_charrefs), dropheadtogether withscriptandstyle, apply NFKC, map U+2019 and U+02BC to', and collapse every run of whitespace, CR and LF included, to one space. Keep line breaks only in the excerpt._mentionsthen runs on the same normalised text, with the needle normalised the same way.Done when
tests/test_imap.pyhas a parametrised test with one message per case above (wrapped plain text with LF and with CRLF, inline bold, , U+00A0, numeric and named entities, U+2019, a phrase split across twodivs) that asserts the kind the unbroken sentence gives; every case fails today.<title>isacknowledged, and one thatmatchfinds a two-word company name split by a line break in the body. Both fail today.Fixed by pull request #25.