Email Workflow
This chapter is mechanics. It is the least intellectually interesting part of contribution and the part that most reliably stops people, because a correctly-reasoned patch that arrives base64-encoded with tabs converted to spaces is indistinguishable from no patch at all.
Everything here has one goal: the bytes you produce must survive the journey and apply cleanly with
git am at the other end. There is a five-minute test that proves it, and you should run it before
you ever mail a stranger.
Why This Is Hard
Modern mail is designed to be pretty. A patch needs to be exact.
| What your mail client wants to do | What it does to a patch |
|---|---|
| Send HTML | The patch is unreadable and most lists drop the message |
| Wrap long lines | Corrupts every line over the wrap width |
| Convert tabs to spaces | Every indented line now fails to apply |
Use format=flowed | Silently reflows the diff |
| Base64-encode | Reviewers cannot quote it inline; many will not read it |
| Attach the patch as a file | Same, plus it defeats the archives' plain-text search |
| "Helpfully" fix your quoting | Breaks threading |
$EDITOR ~/kernel/linux/Documentation/process/email-clients.rst
That document exists because this problem is real, permanent, and has cost every kernel developer at least one embarrassing round trip.
The answer is not to configure your mail client. It is to not use it: git send-email talks
SMTP directly and constructs the message itself.
Configuring git send-email
# Identity. This goes into Signed-off-by, and the DCO requires a real name.
git config --global user.name "Your Real Name"
git config --global user.email "you@example.com"
# SMTP.
git config --global sendemail.smtpServer smtp.example.com
git config --global sendemail.smtpServerPort 587
git config --global sendemail.smtpEncryption tls
git config --global sendemail.smtpUser you@example.com
# Do not put a password in your config. Either leave smtpPass unset (git
# will prompt), or use a credential helper, or point sendemail.smtpServer
# at a local sendmail-compatible binary such as msmtp:
# git config --global sendemail.smtpServer /usr/bin/msmtp
Two settings worth having:
# Do not Cc yourself on every patch.
git config --global sendemail.suppresscc self
# Let get_maintainer.pl pick the recipients (review them before sending).
git config sendemail.tocmd './scripts/get_maintainer.pl --nogit --nogit-fallback --norolestats --nol'
git config sendemail.cccmd './scripts/get_maintainer.pl --nogit --nogit-fallback --norolestats --nom'
Note: If your provider requires 2FA (Gmail, most corporate mail), you need an app-specific password, not your login password. Some corporate mail gateways rewrite messages in transit and cannot be used for patches at all — which is why many kernel developers keep a separate account on a provider that does not. Discover this now, not while sending your first patch.
The Round-Trip Test
Do this before you mail anyone. It takes five minutes and it is the single highest-value thing in this chapter.
# 1. Produce a patch.
git format-patch -1 --base=auto
# 2. Mail it to yourself, and only yourself.
git send-email --to=you@example.com --suppress-cc=all 0001-*.patch
# 3. Fetch the message and save the RAW source — not a "copy as text" from a
# web UI, which will already have mangled it. Then:
git am /tmp/received.eml
# 4. Did it apply? Is the diff byte-identical?
git show --stat HEAD
git diff HEAD~1 HEAD -- . | diff - <(sed -n '/^diff --git/,$p' 0001-*.patch) && echo "IDENTICAL"
If git am applies it cleanly, your mail path is correct and it will be correct for the list. If it
does not, your patch would have been corrupted for everyone, and you have found that out
privately.
# Always dry-run first, too. It shows the exact headers and recipients.
git send-email --dry-run --to=... --cc=... *.patch
Sending for Real
# One patch:
git format-patch -1 --base=auto
./scripts/checkpatch.pl --strict 0001-*.patch
./scripts/get_maintainer.pl 0001-*.patch # decide the recipients
git send-email --dry-run --to='Maintainer <m@x>' --cc='list@vger.kernel.org' 0001-*.patch
git send-email --to='Maintainer <m@x>' --cc='list@vger.kernel.org' 0001-*.patch
# A series, threaded under its cover letter:
git format-patch -v2 --cover-letter --base=auto --thread=shallow <base>..HEAD
$EDITOR v2-0000-cover-letter.patch # the template is EMPTY. Fill it in.
git send-email --to=... --cc=... v2-*.patch
| Flag | Why |
|---|---|
--annotate | Opens each message in your editor before sending — a last look |
--dry-run | Prints headers and recipients, sends nothing |
--suppress-cc=all | For the self-test only |
--no-chain-reply-to | Every patch replies to the cover letter, not to the previous patch. This is the default and it is what you want; a chained thread is unreadable for a long series. |
--in-reply-to=<msgid> | Attach this to an existing thread |
Threading, and Where a New Version Goes
[PATCH 0/3] cover letter ← the thread root
├── [PATCH 1/3] ... ← all three reply to the COVER
├── [PATCH 2/3] ...
└── [PATCH 3/3] ...
└── Re: [PATCH 3/3] ... ← a reviewer's comment
└── Re: [PATCH 3/3] ... ← your inline reply
For a new version, the current guidance in
Documentation/process/submitting-patches.rst is to start a new top-level thread rather than
burying v2 deep inside the v1 discussion, and to point back at the previous version:
Subject: [PATCH v2 0/3] ...
...cover letter...
v1: https://lore.kernel.org/all/<v1-cover-message-id>/
Some subsystems prefer --in-reply-to the v1 cover instead. Check the subsystem profile; when in
doubt, a new thread with a Link: back is never wrong.
b4
b4 is the tool that makes this workflow bearable. Install it and learn four commands.
pip install --user b4 # or your distro's package
b4 --version
Consuming other people's patches
b4 mbox <message-id> # fetch the whole thread as an mbox
b4 am <message-id> # produce an mbox ready for `git am`, with all
# Reviewed-by/Acked-by trailers already collected
b4 shazam <message-id> # fetch AND apply the latest version of the series
b4 diff <message-id> # diff two versions of a series
b4 shazam is how you apply someone's series to review or test it, and it is the fastest way to
start Lab 9. You can pass a message-ID or a lore URL.
Producing your own
b4 prep --new my-feature --fork-point net-next/main # start a tracked series
# ...commit as usual...
b4 prep --edit-cover # the cover letter lives in git
b4 prep --auto-to-cc # fill recipients from get_maintainer
b4 send --dry-run
b4 send
# After review, for v2:
b4 trailers -u # pull Reviewed-by/Tested-by from the list INTO your commits
b4 prep --edit-cover # add the changelog
b4 send
b4 trailers -u is worth the install on its own: it reads the list archive, finds every trailer
people gave you, and applies them to the right commits — which is otherwise a manual, error-prone
copy-paste where the failure mode is putting someone's name on code they did not review.
Reading the Lists Without Drowning
linux-kernel@vger.kernel.org receives thousands of messages a week. Do not subscribe to it.
Three sane strategies:
1. Read lore on the web. No subscription, permalinks, full-text search.
https://lore.kernel.org/all/?q=<terms>
2. Subscribe only to your subsystem's list, which is usually one or two orders of magnitude
smaller. The L: line names it.
3. Use lei — public-inbox's local query tool — to pull only what matches a query into a local
Maildir, and refresh it. This is the modern answer, and it means you never subscribe to anything:
# Everything addressed to you, from every list, into a Maildir:
lei q -o ~/mail/tome -I https://lore.kernel.org/all/ \
--threads --dedupe=mid '(f:you@example.com OR tc:you@example.com)'
lei up ~/mail/tome # refresh later
# Everything touching a file you care about:
lei q -o ~/mail/vkms -I https://lore.kernel.org/all/ \
--threads 'dfn:drivers/gpu/drm/vkms/*'
You must still be reachable: replies come to the address in your From:, so that address has to
work and you have to read it. Missing a reviewer's reply for three weeks is functionally the same as
abandoning the patch.
Tracking Your Patch
Once sent, it exists in three places:
| Where | What it tells you |
|---|---|
lore.kernel.org | It arrived and is public. Search for your subject. |
Patchwork (patchwork.kernel.org) | Its state: New / Under Review / Changes Requested / Accepted / Superseded |
| The maintainer's tree | git fetch <remote> && git log --author="Your Name" <branch> |
Checking Patchwork is how you tell "queued" from "ignored", which is exactly the ambiguity that makes people give up too early.
# Did it land?
git fetch net-next && git log --oneline --author="Your Name" net-next/main | head
# And then, later:
git fetch linux-next --tags && git log --oneline --author="Your Name" linux-next/master | head
Reading Exercise
# 1. Pick any recent series in your subsystem on lore.kernel.org.
# 2. Fetch it and look at the raw message, headers and all:
b4 mbox <message-id> -o /tmp/series
head -40 /tmp/series/*.mbx
# Note: Content-Type is text/plain, there is no base64, the tabs are
# real tabs, and In-Reply-To/References carry the threading.
# 3. Apply it:
b4 shazam <message-id>
git log --oneline -5
# 4. Look at how the trailers were collected:
git show --format='%b' -s HEAD
# 5. Now find the v1 of the same series and diff them:
b4 diff <message-id>
Do this for three series before sending your own. Twenty minutes, and it removes essentially all of the guesswork.
Common Mistakes and Their Symptoms
| Mistake | Symptom | Fix |
|---|---|---|
| Sent from a mail client | Corrupted patch, or silently dropped | git send-email |
| HTML mail | Rejected by the list; no reply | Same |
| Patch as an attachment | No inline review; many reviewers skip it | Same |
| Corporate mail gateway rewrites the body | Applies for you, not for them | Do the round-trip test; use another account if needed |
| Never did the round-trip test | You find out publicly | Do it |
No --dry-run | Mail to the wrong people, unrecallable | Always dry-run |
| Chained reply-to for a 12-patch series | An unreadable thread | --thread=shallow (the default) |
| v2 buried deep in the v1 thread | Reviewers miss it | New thread + a Link: to v1 |
Hand-copied Reviewed-by: trailers | A name on the wrong patch | b4 trailers -u |
| Subscribed to LKML | 3,000 messages a week, then you stop reading anything | Subsystem list, or lei |
Unreachable From: address | The review arrives nowhere | Use an address you read |
| Resending after two days of silence | You annoyed a busy person | Two weeks, once, on the original thread |
Validation / Self-check
- Name four things a normal mail client does that corrupt a patch.
- Why does the kernel want the patch in the message body rather than as an attachment? Give two reasons.
- Describe the round-trip test and say exactly what it proves.
- What does
--dry-runshow you, and why run it every time? - Why is
Signed-off-byincompatible with a pseudonymous email identity? - What does
--thread=shallowdo, and why is chained threading bad for a long series? - Where does a v2 go — a new thread or the old one? What should it contain either way?
- What do
b4 shazamandb4 trailers -udo, and what manual error does each prevent? - Give three ways to follow a mailing list without subscribing to it.
- Your patch was sent nine days ago and there is no reply. Name the three places to look before concluding anything, and what each would tell you.
Next: Review and Etiquette — what to do when they reply, and what to do when they do not.