@@ -17,6 +17,11 @@ final _lenientEmailRegExp = RegExp(r'^\S+@\S+\.\S+$');
1717final _strictEmailRegExp = RegExp (
1818 r'''[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$''' );
1919
20+ /// Matches the local part of the email's message-id field, with relaxed rules:
21+ /// - checks that only valid characters are present
22+ /// - checks for minimum and maximum length
23+ final _messageIdLocalPartRegExp = RegExp (r'^[0-9a-zA-Z\-]{19,36}$' );
24+
2025final _invitesFrom = EmailAddress (
2126 _invitesAtPubDev,
2227 name: 'Dart package site invites' ,
@@ -100,9 +105,15 @@ class EmailMessage {
100105 ///
101106 /// [localMessageId] is not required while in the message construction phase,
102107 /// but a must have when sending out the actual email. `EmailSender`
103- /// implementation must call [verifyLocalMessageId ] before accepting the email
108+ /// implementation must call [verifyLocalMessageIds ] before accepting the email
104109 /// for delivery.
105110 final String ? localMessageId;
111+
112+ /// The local part of a previous email sent by pub.dev, and to which the current
113+ /// email is a reply-to. This will become the local part of the `In-Reply-To` and
114+ /// the `References` SMTP headers.
115+ final String ? inReplyToLocalMessageId;
116+
106117 final EmailAddress from;
107118 final List <EmailAddress > recipients;
108119 final List <EmailAddress > ccRecipients;
@@ -114,19 +125,27 @@ class EmailMessage {
114125 this .recipients,
115126 this .subject,
116127 String bodyText, {
128+ this .inReplyToLocalMessageId,
117129 this .localMessageId,
118130 this .ccRecipients = const < EmailAddress > [],
119131 }) : bodyText = reflowBodyText (bodyText);
120132
121- /// Throws [ArgumentError] if the [localMessageId] field doesn't look like
122- /// UUID or ULID .
133+ /// Throws [ArgumentError] if the [localMessageId] or the
134+ /// [inReplyToLocalMessageId] field doesn't look like an approved ID .
123135 ///
124136 /// TODO: double-check that we follow https://www.jwz.org/doc/mid.html
125- void verifyLocalMessageId () {
126- final uuid = localMessageId;
127- if (uuid == null || uuid.length < 25 || uuid.length > 36 ) {
128- throw ArgumentError ('Invalid uuid: `$uuid `' );
137+ void verifyLocalMessageIds () {
138+ void verifyId (String ? id) {
139+ if (id != null && ! _messageIdLocalPartRegExp.hasMatch (id)) {
140+ throw ArgumentError ('Invalid message-id local part: `$id `' );
141+ }
142+ }
143+
144+ if (localMessageId == null ) {
145+ throw ArgumentError ('`localMessageId` must be initialized.' );
129146 }
147+ verifyId (localMessageId);
148+ verifyId (inReplyToLocalMessageId);
130149 }
131150
132151 Map <String , Object ?> toJson () {
0 commit comments