Repository: dart-lang/pub-dev (dart)

Issue:
Add 'reply-to' header in email_send admin action
We need the action to support an `reply-to` headers.

Pull request description:
Fixes #8514.

Patch:
diff --git a/app/lib/admin/actions/email_send.dart b/app/lib/admin/actions/email_send.dart
index acd3b20a99..97739432df 100644
--- a/app/lib/admin/actions/email_send.dart
+++ b/app/lib/admin/actions/email_send.dart
@@ -38,6 +38,8 @@ The list of resolved emails will be deduplicated.
     'from': 'The email address to impersonate (`support@pub.dev` by default).',
     'subject': 'The subject of the email message.',
     'body': 'The text content of the email body.',
+    'reply-to':
+        '(optional) A comma separated list of email addresses to be used in the Reply-To header.',
     'in-reply-to':
         '(optional) The local message id of the email that this is a reply to '
             '(e.g. moderation case id). The email sender will the `In-Reply-To` and `References` '
@@ -65,6 +67,7 @@ The list of resolved emails will be deduplicated.
     );
     final cc = options['cc'];
     final inReplyTo = options['in-reply-to'];
+    final replyTo = options['reply-to'];
 
     final emailList = await _resolveEmails(to!);
     final ccEmailList = cc == null ? null : await _resolveEmails(cc);
@@ -79,6 +82,7 @@ The list of resolved emails will be deduplicated.
         ccRecipients:
             ccEmailList?.map((v) => EmailAddress(v)).toList() ?? const [],
         inReplyToLocalMessageId: inReplyTo,
+        replyTos: replyTo?.split(',') ?? const <String>[],
       ));
       return {
         'emails': emailList,
diff --git a/app/lib/service/email/email_sender.dart b/app/lib/service/email/email_sender.dart
index 59d8c7ad6e..5c397e5345 100644
--- a/app/lib/service/email/email_sender.dart
+++ b/app/lib/service/email/email_sender.dart
@@ -176,6 +176,7 @@ Message _toMessage(EmailMessage input) {
     'Message-ID': '<${input.localMessageId}@pub.dev>',
     if (inReplyToMessageId != null) 'In-Reply-To': inReplyToMessageId,
     if (inReplyToMessageId != null) 'References': inReplyToMessageId,
+    if (input.replyTos.isNotEmpty) 'Reply-To': input.replyTos.join(','),
   };
   return Message()
     ..headers = headers
diff --git a/app/lib/service/email/email_templates.dart b/app/lib/service/email/email_templates.dart
index 7037964ac9..49cb04184e 100644
--- a/app/lib/service/email/email_templates.dart
+++ b/app/lib/service/email/email_templates.dart
@@ -120,6 +120,7 @@ class EmailMessage {
   final List<EmailAddress> ccRecipients;
   final String subject;
   final String bodyText;
+  final List<String> replyTos;
 
   EmailMessage(
     this.from,
@@ -129,6 +130,7 @@ class EmailMessage {
     this.inReplyToLocalMessageId,
     this.localMessageId,
     this.ccRecipients = const <EmailAddress>[],
+    this.replyTos = const <String>[],
   }) : bodyText = reflowBodyText(bodyText);
 
   /// Throws [ArgumentError] if the [localMessageId] or the
@@ -157,6 +159,7 @@ class EmailMessage {
       'ccRecipients': ccRecipients.map((e) => e.email).toList(),
       'subject': subject,
       'bodyText': bodyText,
+      if (replyTos.isNotEmpty) 'replyTos': replyTos,
     };
   }
 


Test patch:
diff --git a/app/test/admin/email_send_test.dart b/app/test/admin/email_send_test.dart
index d1ad77b262..eece314b4b 100644
--- a/app/test/admin/email_send_test.dart
+++ b/app/test/admin/email_send_test.dart
@@ -43,6 +43,7 @@ void main() {
           'to': 'package:oxygen',
           'subject': 'Test email',
           'body': 'Test body',
+          'reply-to': 'other1@pub.dev,other2@pub.dev',
         }),
       );
       expect(rs1.output, {
@@ -57,7 +58,8 @@ void main() {
         'recipients': ['admin@pub.dev'],
         'ccRecipients': [],
         'subject': 'Test email',
-        'bodyText': 'Test body'
+        'bodyText': 'Test body',
+        'replyTos': ['other1@pub.dev', 'other2@pub.dev'],
       });
     });
   });