This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch docs/plain-text-list-mail-in-skills
in repository https://gitbox.apache.org/repos/asf/struts.git

commit b1aec6932f8e7bdfd0047c7df9bbff1523339b52
Author: Lukasz Lenart <[email protected]>
AuthorDate: Fri Aug 14 09:57:45 2026 +0200

    docs(skills): require plain-text list mail in the two mail-producing skills
    
    ASF lists reject a text/html part outright. Observed on the S2-070
    announcement, 2026-08-14:
    
      <[email protected]>:
      ezmlm-reject: fatal: Sorry, a message part has an unacceptable MIME
      Content-Type: 'text/html' (#5.2.3)
    
      <[email protected]>:
      Must be sent from an @apache.org address.
    
    [email protected] accepted the same message, so the HTML advisory
    reached one list while another rejected it -- a missing bounce is not
    evidence the format was right.
    
    A draft created through the Gmail tool is an HTML draft whatever is
    passed to it: with body alone, Gmail still synthesises a text/html
    alternative on send, linkifies the URLs and reflows the plain part. The
    vote-mail skill's table said body-only was "the least-bad, and is what
    to use", which is wrong -- no tool argument produces a plain-text mail.
    The format is decided in the compose window.
    
    Both skills now state the deliverable as a contract: draft with body and
    never htmlBody, the identical body in a file wrapped at 72 columns, and
    a handover that says to switch on plain-text mode, paste the file, and
    send from the @apache.org identity.
    
    creating-security-bulletins gains the announcement step it never had,
    pointing at the CVE tool's OSS/ASF Emails tab -- both mails are
    generated from the record, so copying them keeps the advisory and the
    CVE from drifting. Publication is also spelled out: clear read and
    update restrictions completely, and verify with an unauthenticated
    fetch rather than trusting the API's success response.
    
    Co-authored-by: Claude Opus 5 <[email protected]>
---
 .claude/skills/creating-release-vote-mail/SKILL.md | 53 ++++++++++++++++-----
 .../vote-mail-template.md                          |  9 ++--
 .../skills/creating-security-bulletins/SKILL.md    | 55 +++++++++++++++++++++-
 3 files changed, 101 insertions(+), 16 deletions(-)

diff --git a/.claude/skills/creating-release-vote-mail/SKILL.md 
b/.claude/skills/creating-release-vote-mail/SKILL.md
index 4604faaf9..e34815b88 100644
--- a/.claude/skills/creating-release-vote-mail/SKILL.md
+++ b/.claude/skills/creating-release-vote-mail/SKILL.md
@@ -92,22 +92,47 @@ path you hand over. **Never send.**
 Sending opens a binding vote on a permanently archived public list and commits 
the PMC to the
 artifacts as staged.
 
-### Gmail mangles the links — hand over a paste-ready body
+### The mail must be text/plain, and no tool argument achieves that
 
-Gmail's linkifier rewrites URLs server-side as the draft is stored, so the 
four link lines
-arrive as `https://www.google.com/url?q=...&source=gmail&ust=...` and the 
72-column wrap is
-reflowed. There is no way to pass the body through the Gmail tool that avoids 
it:
+An ASF list mail carries **one `text/plain` part and nothing else**. A 
`text/html` part is a
+delivery failure, not a cosmetic one — `[email protected]` rejects it 
permanently:
 
-| Body passed as | Result |
+```
+ezmlm-reject: fatal: Sorry, a message part has an unacceptable MIME 
Content-Type: 'text/html' (#5.2.3)
+```
+
+**A draft created through the Gmail tool is an HTML draft, whatever you pass 
it.** Gmail
+synthesises a `text/html` alternative when the draft is sent, linkifies every 
URL into
+`<a href>`, and reflows the plain part:
+
+| Body passed as | What is actually sent |
 |---|---|
-| `body` only | Wrapped hrefs; plain rendering shows `bare-url 
<google.com/url?q=…>` |
-| `htmlBody` only | No plain-text part at all — HTML-only mail, wrong for an 
ASF list |
-| both | Worst: the plain part's *visible* text becomes the wrapped URL |
+| `body` only | `multipart/alternative` — the HTML part is generated for you |
+| `htmlBody` only | HTML-only, no plain part at all |
+| both | Same, plus the plain part's visible text becomes the wrapped URL |
+
+Gmail's linkifier also rewrites URLs server-side, so link lines can arrive as
+`https://www.google.com/url?q=...&source=gmail&ust=...`.
+
+**Do not rely on a bounce to catch this.** On the 2026-08-14 advisory run the 
same message was
+rejected by `[email protected]` and accepted by `[email protected]` — 
the HTML mail
+reached one list and not the other, from a single send.
+
+So the deliverable is three things, and it is incomplete without any of them:
+
+1. A Gmail draft with To, Bcc, Subject and `body`. **Never `htmlBody`.**
+2. The identical body written to a file, whose path you hand over.
+3. In your handover, the sending instruction: **switch the compose window to 
plain-text mode**
+   (⋮ → *Plain text mode*), then select-all and paste the file over the body.
+
+Step 3 is what actually produces the plain-text mail; steps 1 and 2 only make 
it one paste
+instead of four hand-edited URLs. Hard-wrap the file at 72 columns — a paste 
into plain-text
+mode keeps the wrapping the file has, and Gmail reflows anything longer.
 
-`body` only is the least-bad, and is what to use. **Also write the exact body 
to a file and
-give the release manager its path.** Pasting plain text over the compose 
window restores both
-the bare URLs and the wrap, making the fix one select-all-paste instead of 
four hand-edited
-URLs.
+**Say which identity to send from: `@apache.org`.** The Gmail account's 
default sender is a
+personal address, and a vote arriving in the `dev@` archive from one reads as 
an outsider
+calling a PMC vote. Some ASF lists refuse it outright — 
`[email protected]`
+answers *"Must be sent from an @apache.org address."*
 
 **Never re-run the draft-update tool on a draft whose links have already been 
fixed by hand** —
 it re-mangles them. A draft the release manager has corrected is finished; 
leave it alone.
@@ -141,6 +166,8 @@ A vote opened on a 404 burns the window before anyone can 
test.
 - Severity, CVE, S2-XXX, bulletin link or reporter detail anywhere, on any 
channel
 - `[email protected]` in any header, including Cc
 - Sending rather than drafting
+- `htmlBody` passed to the draft tool, for any reason
+- A draft handed over without the plain-text-mode instruction and the body file
 - A new paragraph inserted into the vote boilerplate
 - A quality checkbox arriving pre-ticked
 - An opening sentence carried over from the previous release
@@ -158,3 +185,5 @@ A vote opened on a 404 burns the window before anyone can 
test.
 | "I'm adding to the boilerplate, not changing it" | Insertion is editing. The 
vote call is byte-frozen. |
 | "The release notes leave out what integrators need" | Then the page needs 
fixing. The mail renders the page. |
 | "Last release's opening sentence fits" | It described last release. Write 
the one this list supports. |
+| "I passed `body`, not `htmlBody`, so it's plain text" | Gmail generates the 
HTML part itself. The format is set in the compose window. |
+| "It went through last time, so HTML is tolerated" | Lists differ. One 
accepted the same message the other rejected. |
diff --git a/.claude/skills/creating-release-vote-mail/vote-mail-template.md 
b/.claude/skills/creating-release-vote-mail/vote-mail-template.md
index 339e72649..8db52adb8 100644
--- a/.claude/skills/creating-release-vote-mail/vote-mail-template.md
+++ b/.claude/skills/creating-release-vote-mail/vote-mail-template.md
@@ -114,9 +114,10 @@ On behalf of the Apache Struts project
 ```
 
 Hard-wrap the body at 72 columns, continuation lines unindented, so the list 
stays legible in
-the ASF archives and in quoted replies. Gmail reflows the wrap and rewrites 
the link lines when
-it stores the draft — see *Gmail mangles the links* in [`SKILL.md`](SKILL.md) 
for why the body
-also has to be handed over as a file.
+the ASF archives and in quoted replies. Gmail reflows the wrap, rewrites the 
link lines, and
+adds a `text/html` part that ASF lists reject — see *The mail must be 
text/plain* in
+[`SKILL.md`](SKILL.md) for why the body also has to be handed over as a file, 
and what the
+release manager has to do in the compose window before sending.
 
 ## Frozen text
 
@@ -147,3 +148,5 @@ repository does not.
 - [ ] Exactly one mail
 - [ ] Body also saved to a file and its path handed over, so the mangled links 
and wrap can be
       fixed with one paste
+- [ ] `htmlBody` not passed to the draft tool
+- [ ] Handover states: plain-text mode on, paste the file, send from the 
`@apache.org` identity
diff --git a/.claude/skills/creating-security-bulletins/SKILL.md 
b/.claude/skills/creating-security-bulletins/SKILL.md
index 3af4e66a7..864908c1c 100644
--- a/.claude/skills/creating-security-bulletins/SKILL.md
+++ b/.claude/skills/creating-security-bulletins/SKILL.md
@@ -1,6 +1,6 @@
 ---
 name: creating-security-bulletins
-description: Use when drafting, updating, or reviewing an S2-XXX security 
bulletin on the Struts cwiki, when preparing bulletin text ahead of a CVE 
request, or when deciding how much detail about a fixed vulnerability is safe 
to publish.
+description: Use when drafting, updating, or reviewing an S2-XXX security 
bulletin on the Struts cwiki, when preparing bulletin text ahead of a CVE 
request, when publishing a bulletin and announcing it to the ASF lists, or when 
deciding how much detail about a fixed vulnerability is safe to publish.
 ---
 
 # Creating Security Bulletins
@@ -159,6 +159,52 @@ Bulletins stay restricted until the coordinated 
publication date.
 
 Expected on the Struts wiki: read and update limited to the author plus 
`struts-committers`.
 
+Publication is clearing them **completely** — read *and* update, both empty, 
matching every
+already-published bulletin. Verify with an unauthenticated fetch of the public 
URL, not with the
+API's response: the tool reporting success is not the page being readable.
+
+## Announcing it: the mail is text/plain, or it does not arrive
+
+Once the page is public the advisory goes to the lists. **The mail carries one 
`text/plain`
+part and nothing else.** A `text/html` part is a delivery failure — 
`[email protected]`
+rejects it permanently:
+
+```
+ezmlm-reject: fatal: Sorry, a message part has an unacceptable MIME 
Content-Type: 'text/html' (#5.2.3)
+```
+
+**Do not rely on a bounce to catch it.** On the S2-070 run, 2026-08-14, one 
send was rejected
+by `[email protected]` and *accepted* by `[email protected]`. The HTML 
advisory reached
+the user list. A partial failure looks like success in the Sent folder.
+
+Two unrelated defects bounced that morning, each from a different list:
+
+| Defect | What the list says |
+|---|---|
+| A `text/html` part | `unacceptable MIME Content-Type: 'text/html' (#5.2.3)` |
+| Wrong sender identity | `Must be sent from an @apache.org address.` |
+
+**The CVE tool generates both mails — use them.** Each record on 
`cveprocess.apache.org` has an
+*OSS/ASF Emails* tab holding a finished `oss-security` mail and a finished 
ASF-lists mail, built
+from the record's own affected ranges, description, credit and references, 
with send buttons
+that go through ASF infrastructure rather than a personal mailbox. Copying 
that text is how the
+mail stays consistent with the CVE record; composing a fresh one is how the 
two drift.
+
+If you draft in Gmail instead, the deliverable is three things and is 
incomplete without any:
+
+1. A draft with To, Bcc, Subject and `body`. **Never `htmlBody`** — and 
passing `body` alone
+   does not make the mail plain text; Gmail generates the HTML part itself on 
send.
+2. The identical body in a file, whose path you hand over, hard-wrapped at 72 
columns.
+3. The sending instruction in your handover: **plain-text mode on** (⋮ → 
*Plain text mode*),
+   paste the file over the body, send from the `@apache.org` identity.
+
+The `oss-security` copy is a separate mail with no Cc and no Bcc — not the ASF 
mail with an
+extra recipient.
+
+**Recipients are not interchangeable.** The tool's ASF mail addresses 
`[email protected]` and
+`dev@`; Struts practice adds `[email protected]`, which is the list 
operators actually
+read. `[email protected]` takes only `@apache.org` senders.
+
 ## Start from the template, never from a previous bulletin
 
 **[`bulletin-template.md`](bulletin-template.md)** — the field reference, 
per-section guidance, pre-publication checklist, and a storage-format skeleton 
ready to POST to the Confluence API. **It is the source of truth.**
@@ -189,6 +235,10 @@ Read the whole page and rewrite it; do not patch the 
fields you happen to notice
 - No statement of who is *not* affected, when exposure depends on a plugin or 
an opt-in setting
 - Writing a page from content you read earlier in the session without 
re-fetching it first
 - Publishing without re-checking restrictions
+- Treating an API success as proof the page is publicly readable
+- `htmlBody` passed to the draft tool, for any reason
+- An announcement composed from scratch when the CVE record's *OSS/ASF Emails* 
tab holds one
+- A draft handed over without the plain-text-mode instruction and the body file
 - A severity rating chosen by feel, or by reachability alone, without checking 
it against the published scale
 - Rating something Low because the feature is opt-in — opt-in is the 
definition of Moderate
 
@@ -211,3 +261,6 @@ Read the whole page and rewrite it; do not patch the fields 
you happen to notice
 | "The patch is reviewed, so the release will contain it" | Reviewed is not 
merged. Re-check at publication, not at drafting. |
 | "Copying the last bulletin is quicker than the template" | It is how another 
advisory's CVE ships on your page. Copy the template. |
 | "I read the page a few minutes ago" | Someone else may have written to it 
since. Re-fetch, then write. There is no conflict warning. |
+| "I passed `body`, not `htmlBody`, so it's plain text" | Gmail generates the 
HTML part itself on send. The format is decided in the compose window. |
+| "It reached the lists, so the format was fine" | One list accepted the same 
message another rejected. Check every recipient, not the Sent folder. |
+| "Writing the mail myself is quicker than opening the CVE tool" | The tool's 
text is generated from the record. Hand-written text is how the mail and the 
CVE drift apart. |

Reply via email to