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 doWhat it does to a patch
Send HTMLThe patch is unreadable and most lists drop the message
Wrap long linesCorrupts every line over the wrap width
Convert tabs to spacesEvery indented line now fails to apply
Use format=flowedSilently reflows the diff
Base64-encodeReviewers cannot quote it inline; many will not read it
Attach the patch as a fileSame, plus it defeats the archives' plain-text search
"Helpfully" fix your quotingBreaks 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
FlagWhy
--annotateOpens each message in your editor before sending — a last look
--dry-runPrints headers and recipients, sends nothing
--suppress-cc=allFor the self-test only
--no-chain-reply-toEvery 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:

WhereWhat it tells you
lore.kernel.orgIt arrived and is public. Search for your subject.
Patchwork (patchwork.kernel.org)Its state: New / Under Review / Changes Requested / Accepted / Superseded
The maintainer's treegit 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

MistakeSymptomFix
Sent from a mail clientCorrupted patch, or silently droppedgit send-email
HTML mailRejected by the list; no replySame
Patch as an attachmentNo inline review; many reviewers skip itSame
Corporate mail gateway rewrites the bodyApplies for you, not for themDo the round-trip test; use another account if needed
Never did the round-trip testYou find out publiclyDo it
No --dry-runMail to the wrong people, unrecallableAlways dry-run
Chained reply-to for a 12-patch seriesAn unreadable thread--thread=shallow (the default)
v2 buried deep in the v1 threadReviewers miss itNew thread + a Link: to v1
Hand-copied Reviewed-by: trailersA name on the wrong patchb4 trailers -u
Subscribed to LKML3,000 messages a week, then you stop reading anythingSubsystem list, or lei
Unreachable From: addressThe review arrives nowhereUse an address you read
Resending after two days of silenceYou annoyed a busy personTwo weeks, once, on the original thread

Validation / Self-check

  1. Name four things a normal mail client does that corrupt a patch.
  2. Why does the kernel want the patch in the message body rather than as an attachment? Give two reasons.
  3. Describe the round-trip test and say exactly what it proves.
  4. What does --dry-run show you, and why run it every time?
  5. Why is Signed-off-by incompatible with a pseudonymous email identity?
  6. What does --thread=shallow do, and why is chained threading bad for a long series?
  7. Where does a v2 go — a new thread or the old one? What should it contain either way?
  8. What do b4 shazam and b4 trailers -u do, and what manual error does each prevent?
  9. Give three ways to follow a mailing list without subscribing to it.
  10. 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.