In a multipart email, which body part should an automated check read its evidence from?
answer
- the parts are alternatives, not copies
- decode before you compare
- pick the part your check proves
- a missing part is a failure
basics
~20 sRead the part your check actually needs: the plain-text alternative for text evidence, the formatted part only when its markup is the point. Decode and normalise before matching, and fail loudly when the expected part is missing.
solid answer
~50 sThe parts are **alternatives, not copies**. Both are produced from one template, but the plain-text alternative is usually a stripped rendering — emphasis gone, tables flattened, lines re-wrapped at a fixed width — while the formatted part is markup, where the words a reader sees are interleaved with tags and escapes. For most functional checks you are proving that a value the flow produced arrived intact, and the plain-text alternative is the cheapest place to see it. Decode using the part's own declared encoding and character set, normalise whitespace, then match a short distinctive value rather than a whole sentence a soft wrap can split. Read the formatted part only when its markup is the subject, and extract text before matching. If the part you rely on is absent, fail with that as the stated cause — a silent fallback turns a template change into a green run.
code
pseudocode · 18 linesparts = message.parts # each: kind, encoding, charset, disposition, content
plain = parts.first(p -> p.kind == "plain-text")
if plain == null:
fail("no plain-text alternative on the captured message")
body = normalise_whitespace(
decode(plain.content, plain.encoding, plain.charset))
assert body contains order_reference # short, distinctive, wrap-proof
assert body contains formatted_total
# attachments are parts too, with metadata of their own
receipt = parts.first(p -> p.disposition == "attachment")
if receipt == null:
fail("no attachment part on the captured message")
assert receipt.name starts_with("receipt-" + order_reference)
assert receipt.bytes.size > 0go deeper
Recall that one notification can carry more than one body part — a plain-text alternative and a formatted one — and that they are generated separately, so their text is not guaranteed to match word for word.
Explain the mechanics: select the part by kind, decode it with its own declared encoding and character set, normalise whitespace, then compare. Be able to say why a raw match against markup misses text a reader can see.
Show judgement about which part proves which claim, about intermittent failures caused by soft wrapping, and about refusing a silent fallback when the expected part is absent so a template change cannot pass unnoticed.
Own the convention for the whole suite: which part functional checks read, how small and distinctive a matched value must be, and where appearance is checked deliberately instead of leaking into every notification case.
## Two parts, one message, two spellings A multipart message carries the same notification more than once: a plain-text rendering and a formatted one, generated from the same template, each declaring its own character set and transfer encoding. Larger messages add further parts — inline images the formatted part refers to, and attachments, which are parts marked "save this" rather than "show this". The crucial point is that the parts are **alternatives, not copies**. Because both come from one template, people assume the text is identical. It routinely is not. The plain-text alternative is often generated by stripping the formatted one, which loses emphasis, collapses tables into runs of text, and re-wraps long lines at a fixed width. The formatted part, read raw, is markup: the words a reader sees are interleaved with tags and escapes, and the renderer may insert breaks anywhere. ## Pick the part by what the check proves | What the check proves | Read | Why | |---|---|---| | A value the flow produced reached the recipient — a name, an amount, a reference | The plain-text alternative | Shortest path from stored bytes to comparable text | | The right notification fired at all | The subject line and sender field | Cheaper and far more stable than any body match | | A promised document was delivered | The attachment part's metadata and bytes | The body says nothing at all about it | | The formatted rendering itself is correct | The formatted part, extracted to text or rendered on purpose | The only case where the markup is the subject | The rule underneath the table: **read the part the product commits to, at the smallest granularity that still proves your point**. Most functional checks are proving that a value computed by the flow arrived intact, and the plain-text alternative is the cheapest place to see it. ## Decode and normalise before you compare Raw bytes are not text, and a comparison against raw bytes fails in ways that look like product bugs: - **Transfer encoding.** Part contents are encoded for transport. Compare after decoding, never before. - **Character set.** Accented names, non-Latin scripts and currency symbols compare wrong when the declared character set is ignored. - **Soft line wrapping.** Long lines are broken at a fixed width. A match on a long sentence fails whenever the wrap lands mid-sentence — and it lands differently when a name is one character longer, which is exactly why this failure looks intermittent. - **Markup and escapes in the formatted part.** Text the reader sees plainly may be split across tags, or spelled as an escape sequence in the source. Extract text first, or do not match against that part. - **Whitespace.** Collapse runs of spaces and newlines before comparing, and match on short distinctive phrases or single field values rather than whole sentences. ## A missing part is a result, not a fallback Suppose your check reads the plain-text alternative and one day the message stops carrying one. The tempting behaviour is to fall back to the other part and carry on. Resist it: the fallback converts a real change in what the product emits — a template edited so it no longer produces an alternative — into a green run, and it does so on exactly the notification you were watching. Fail with a named cause instead: *the message carried no plain-text alternative*. If reading the formatted part is genuinely acceptable for a given check, make that an explicit, recorded decision for that check, with text extraction in front of the match — not an implicit rescue that hides why the message changed shape. The same discipline applies to the opposite direction. A check that reads the formatted part and quietly succeeds when the part is empty is asserting nothing, and it will keep passing long after the template stops rendering. ## Attachments are parts too An attachment is not a special object; it is a part whose disposition says it should be saved rather than displayed. It has a filename, a declared type, a size and bytes. A check that asserts only its presence passes on a zero-byte file and on the wrong document under the right name. Assert the name pattern the product commits to, the declared type, a plausible non-zero size, and — where the document's content is the promise — one value parsed from inside it. ## Putting it together For a typical functional check the order is fixed: 1. **Locate** the part you need by its kind, rather than taking whichever part comes first. 2. **Fail loudly** if that part is absent, instead of falling back to another one. 3. **Decode** it using its own declared transfer encoding and character set. 4. **Normalise** whitespace so soft wrapping cannot break a comparison. 5. **Match** the smallest distinctive thing that proves the flow worked — a reference, an amount, a name. Assertions built that way survive template edits that change wording and layout, and they still fail when the value the flow produced is wrong. Assertions built the other way — a long sentence matched against raw markup, with a silent fallback when the part is missing — fail on rewording, pass on regressions, and teach the team that the notification checks are unreliable rather than that the notifications are.
- A notification stops carrying a plain-text alternative. Why not simply fall back to the other part?Because the fallback hides the very change you would want to hear about: a template edited so it no longer emits an alternative. The run goes green on a real difference in what the product sends. Fail with the absence as the named cause, and if reading the formatted part is acceptable for that check, make it an explicit recorded decision with text extraction in front of the match.
- Why can a literal match against the formatted part miss text a reader plainly sees?Because the raw source is markup, not the reader's view. The words are interleaved with tags, some characters are written as escape sequences, and the renderer may break lines anywhere. A phrase that reads as one sentence on screen can be split across several tags in the source, so the match needs extracted text rather than the raw part.
- How do you keep a text assertion from breaking on line wrapping?Normalise first — collapse runs of spaces and newlines into single spaces — then match something short and distinctive: a reference, an amount, a name. Long sentence matches fail whenever a soft wrap lands mid-phrase, and the wrap position moves when a neighbouring value changes length, which is why the failure looks intermittent rather than deterministic.
The two parts are two translations of one notice rather than two photocopies: quoting from one while checking the other is exactly how a difference slips through unnoticed.
saying these in an interview costs you the question
- Matches raw markup and calls it reading the message
- Falls back silently to the other part when one is missing
- Matches a long sentence that line wrapping can split
- Ignores the declared encoding and character set before comparing
- Assumes both parts always carry identical wording
- Treats an attachment's presence as proof of its contents