public inbox for tools@linux.kernel.org
 help / color / mirror / Atom feed
* [PATCH] b4: Implement --bcc and send-series-bcc config option
@ 2026-03-07  1:27 Dmitry Torokhov
  2026-03-07  4:49 ` Konstantin Ryabitsev
  0 siblings, 1 reply; 2+ messages in thread
From: Dmitry Torokhov @ 2026-03-07  1:27 UTC (permalink / raw)
  To: tools

Allow using blind carbon copy when sending entire series (no per-patch
Bcc).

Handling is similar to "To" and "Cc" handling except that it ignores
"not-me-too" and other exclusion lists as the intent to send to the
address(es) specified in bcc is explicit.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 docs/config.rst           |  9 +++++++++
 docs/contributor/send.rst | 11 ++++++++++-
 src/b4/__init__.py        | 19 +++++++++++++------
 src/b4/command.py         |  1 +
 src/b4/ez.py              | 23 ++++++++++++++++++++++-
 5 files changed, 55 insertions(+), 8 deletions(-)

diff --git a/docs/config.rst b/docs/config.rst
index 8ffbf5e..9f508cf 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -591,6 +591,15 @@ Contributor-oriented settings
      .. versionchanged:: v0.15
         Added ``shallow`` config value.
 
+   :term:`b4.send-series-bcc`
+     A comma-separated list of addresses to always add to the "Bcc:" list.
+     Recipients in this list will always receive the series regardless of
+     any exclusion filters (such as the ``--not-me-too`` flag or the
+     :term:`b4.email-exclude` configuration setting).
+     See :ref:`prep_recipients`.
+
+     Default: ``None``
+
    :term:`b4.send-series-cc`
      A comma-separated list of addresses to always add to the "Cc:" header.
      See :ref:`prep_recipients`.
diff --git a/docs/contributor/send.rst b/docs/contributor/send.rst
index ba18ef5..1314d48 100644
--- a/docs/contributor/send.rst
+++ b/docs/contributor/send.rst
@@ -320,8 +320,17 @@ Command line flags
   configuration file using the :term:`b4.send-series-cc` option (see
   :ref:`contributor_settings`).
 
+``--bcc``
+  Additional email addresses to include into the Bcc: list. Recipients
+  in this list will always receive the series regardless of any
+  exclusion filters (such as the ``--not-me-too`` flag or the
+  :term:`b4.email-exclude` configuration setting). Separate multiple
+  entries with a comma. You can also set this in the configuration
+  file using the :term:`b4.send-series-bcc` option (see
+  :ref:`contributor_settings`).
+
 ``--not-me-too``
-  Removes your own email address from the recipients.
+  Removes your own email address from the To: or Cc: recipients.
 
 ``--no-sign``
   Don't sign your patches with your configured attestation mechanism.
diff --git a/src/b4/__init__.py b/src/b4/__init__.py
index 9a5d25b..9ad68a4 100644
--- a/src/b4/__init__.py
+++ b/src/b4/__init__.py
@@ -4413,6 +4413,14 @@ def send_mail(smtp: Union[smtplib.SMTP, smtplib.SMTP_SSL, List[str], None], msgs
         dryrun = True
 
     for msg in msgs:
+        if not destaddrs:
+            alldests = email.utils.getaddresses([str(x) for x in msg.get_all('to', [])])
+            alldests += email.utils.getaddresses([str(x) for x in msg.get_all('cc', [])])
+            alldests += email.utils.getaddresses([str(x) for x in msg.get_all('bcc', [])])
+            myaddrs = {x[1] for x in alldests}
+        else:
+            myaddrs = set(destaddrs)
+
         if not msg.get('X-Mailer'):
             msg.add_header('X-Mailer', f'b4 {__VERSION__}')
         msg.set_charset('utf-8')
@@ -4422,6 +4430,11 @@ def send_mail(smtp: Union[smtplib.SMTP, smtplib.SMTP_SSL, List[str], None], msgs
         else:
             nl = '\r\n'
 
+        # If we have a Bcc header, we must strip it from the message body
+        # before sending it out.
+        if msg['Bcc']:
+            del msg['Bcc']
+
         bdata = LoreMessage.get_msg_as_bytes(msg, nl=nl, headers='encode')
 
         subject = msg.get('Subject', '')
@@ -4450,12 +4463,6 @@ def send_mail(smtp: Union[smtplib.SMTP, smtplib.SMTP_SSL, List[str], None], msgs
             logger.info('    | ' + bdata.decode().rstrip().replace('\n', '\n    | '))
             logger.info('    --- DRYRUN: message ends ---')
             continue
-        if not destaddrs:
-            alldests = email.utils.getaddresses([str(x) for x in msg.get_all('to', [])])
-            alldests += email.utils.getaddresses([str(x) for x in msg.get_all('cc', [])])
-            myaddrs = {x[1] for x in alldests}
-        else:
-            myaddrs = set(destaddrs)
 
         tosend.append((myaddrs, bdata, ls))
 
diff --git a/src/b4/command.py b/src/b4/command.py
index a49a8bc..d115765 100644
--- a/src/b4/command.py
+++ b/src/b4/command.py
@@ -425,6 +425,7 @@ def setup_parser() -> argparse.ArgumentParser:
                          help='Do not add any addresses found in the cover or patch trailers to To: or Cc:')
     sp_send.add_argument('--to', nargs='+', metavar='ADDR', help='Addresses to add to the To: list')
     sp_send.add_argument('--cc', nargs='+', metavar='ADDR', help='Addresses to add to the Cc: list')
+    sp_send.add_argument('--bcc', nargs='+', metavar='ADDR', help='Addresses to add to the Bcc: list')
     sp_send.add_argument('--not-me-too', action='store_true', default=False,
                          help='Remove yourself from the To: or Cc: list')
     sp_send.add_argument('--resend', metavar='vN', nargs='?', const='latest',
diff --git a/src/b4/ez.py b/src/b4/ez.py
index 52eb239..378d201 100644
--- a/src/b4/ez.py
+++ b/src/b4/ez.py
@@ -1968,6 +1968,8 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
     excludes: Set[str] = set()
     pccs: Dict[str, List[Tuple[str, str]]] = dict()
 
+    bccdests = list()
+
     if cmdargs.preview_to or cmdargs.no_trailer_to_cc:
         todests = list()
         ccdests = list()
@@ -2006,6 +2008,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
 
     tos = set()
     ccs = set()
+    bccs = set()
     if cmdargs.preview_to:
         tos.update(cmdargs.preview_to)
     else:
@@ -2017,6 +2020,13 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
             ccs.update(cmdargs.cc)
         if config.get('send-series-cc'):
             ccs.add(config.get('send-series-cc'))
+        if cmdargs.bcc:
+            bccs.update(cmdargs.bcc)
+        if config.get('send-series-bcc'):
+            bccs.add(config.get('send-series-bcc'))
+    if bccs:
+        for pair in email.utils.getaddresses(list(bccs)):
+            bccdests.append(pair)
     if ccs:
         for pair in email.utils.getaddresses(list(ccs)):
             if pair[1] in seen:
@@ -2032,6 +2042,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
 
     allto = list()
     allcc = list()
+    allbcc = list()
     alldests = set()
 
     if todests:
@@ -2042,6 +2053,10 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
         allcc = b4.cleanup_email_addrs(ccdests, excludes, None)
         logger.debug('allcc: %s', allcc)
         alldests.update(set([x[1] for x in allcc]))
+    if bccdests:
+        allbcc = b4.cleanup_email_addrs(bccdests, set(), None)
+        logger.debug('allbcc: %s', allbcc)
+        alldests.update(set([x[1] for x in allbcc]))
 
     logger.debug('alldests: %s', alldests)
 
@@ -2076,7 +2091,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
 
     # Give the user the last opportunity to bail out
     if not cmdargs.dryrun:
-        if not len(alldests):
+        if not len(allto):
             logger.critical('CRITICAL: Could not find any destination addresses')
             logger.critical('          try b4 prep --auto-to-cc or b4 send --to addr')
             sys.exit(1)
@@ -2135,6 +2150,7 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
         logger.info('---')
         b4.print_pretty_addrs(allto, 'To')
         b4.print_pretty_addrs(allcc, 'Cc')
+        b4.print_pretty_addrs(allbcc, 'Bcc')
         logger.info('---')
         for commit, msg in patches:
             if not msg:
@@ -2256,6 +2272,11 @@ def cmd_send(cmdargs: argparse.Namespace) -> None:
                 msg.replace_header('Cc', b4.format_addrs(pcc))
             else:
                 msg.add_header('Cc', b4.format_addrs(pcc))
+        if allbcc:
+            if msg['Bcc']:
+                msg.replace_header('Bcc', b4.format_addrs(allbcc))
+            else:
+                msg.add_header('Bcc', b4.format_addrs(allbcc))
 
         send_msgs.append(msg)
 
-- 
2.53.0.473.g4a7958ca14-goog


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] b4: Implement --bcc and send-series-bcc config option
  2026-03-07  1:27 [PATCH] b4: Implement --bcc and send-series-bcc config option Dmitry Torokhov
@ 2026-03-07  4:49 ` Konstantin Ryabitsev
  0 siblings, 0 replies; 2+ messages in thread
From: Konstantin Ryabitsev @ 2026-03-07  4:49 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: tools

On Fri, Mar 06, 2026 at 05:27:45PM -0800, Dmitry Torokhov wrote:
> Allow using blind carbon copy when sending entire series (no per-patch
> Bcc).
> 
> Handling is similar to "To" and "Cc" handling except that it ignores
> "not-me-too" and other exclusion lists as the intent to send to the
> address(es) specified in bcc is explicit.

I think this can be done a lot simpler, tbh. We don't need to bother adding
Bcc: headers just to strip them right away -- we can just handle this directly
in the send_mail() method in __init__.py. Before we hand off the delivery to
the command or smtp agent, we insert the bcc address(es) directly into the
list of destaddrs and don't bother with the cosmetic headers. This way
everything gets this functionality -- not just sending via "b4 send", but also
sending via "b4 review".

That said, I can't take this yet, because without the server-side change in
the web endpoint, this won't do the right thing. Let me sit on this for a
bit until I'm done with the server upgrades, and then I'll revisit this.

Thanks!

-- 
KR

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-03-07  4:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-07  1:27 [PATCH] b4: Implement --bcc and send-series-bcc config option Dmitry Torokhov
2026-03-07  4:49 ` Konstantin Ryabitsev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox