Message-Id invalide selon RFC 2822 + encoding

Bonjour,

Je remonte deux problÚmes relatifs aux emails envoyés.

Message-Id Invalide

Suite Ă  une analyse avec https://www.mail-tester.com/,
Il semblerait que le â€č Message-Id â€ș ne soit pas valide.

L’erreur mail-tester est :
INVALID_MSGID Message-Id is not valid, according to RFC 2822

Voici l’en-tĂȘte « Message-Id Â» selon mail-tester :

Message-Id: =?utf-8?q?=3C165306032961=2E3393=2E9803830538757387937=2EKyW?=
 =?utf-8?q?aOn82WZ=40www=2Eatelierdusoleiletduvent=2Eorg=3E?=

Peut-ĂȘtre un problĂšme d’encodage ou de dĂ©finition d’encodage ?

En effet, dans le « Journal des Emails Â» sous Dokos, le Message-Id est :
Message-Id: <165306032961.3393.9803830538757387937.KyWaOn82WZ@www.atelierdusoleiletduvent.org>

Avez-vous le mĂȘme problĂšme ?
Faut-il dĂ©finir le type d’encodage des en-tĂȘtes du mail ?

Encodage du champ â€č sujet â€ș

Aussi, j’ai une erreur sur l’encodage du champ sujet qui Ă©tait « Bon de rĂ©servation Â»
DĂ©tails mail-tester :

-0.1 SUBJECT_NEEDS_ENCODING Subject is encoded but does not specify the encoding
-1.105 SUBJ_ILLEGAL_CHARS Subject: has too many raw illegal characters

1 « J'aime »

Bonjour,

Merci d’avoir fait remonter ce problùme.

Il semblerait que la politique d’encodage des e-mails utilisĂ©e par Dokos, SMTPUTF8, ne convertit pas les charactĂšres non-ASCII Ă  l’intĂ©rieur des headers. VoilĂ  l’explication pour l’erreur de Subject. NĂ©anmoins, je n’ai pas rĂ©ussi Ă  reproduire le problĂšme du Message-Id, qui n’est pas censĂ© ĂȘtre encodĂ© en Quoted-Printable peu importe son contenu, ni mĂȘme dĂ©coupĂ© en plusieurs lignes (bien que cela soit autorisĂ©).1

Apparemment, d’aprĂšs le test que vous avez effectuĂ©, c’est SpamAssassin qui donne un mauvais score au mail pour deux raisons : le Message-Id ne devrait pas ĂȘtre encodĂ© (mĂȘme en Quoted-Printable), et le header Subject ne devrait pas contenir de caractĂšres non-ASCII.

Une correction qui vise Ă  changer la politique d’encodage vers SMTP, donc qui permettra d’amĂ©liorer le score SpamAssassin des mails envoyĂ©s par Dokos, est disponible ici : dokos/dodock #14.


1. Il me semble que l’usage des caractĂšres non-ASCII dans les headers est parfois autorisĂ©, sinon il peut se faire par des encodages comme Base64 ou Quoted-Printable.[RFC 6531, RFC 6532, SpamAssassin, rspamd]

2 « J'aime »

Bonjour Corentin,

Au top, merci pour ces détails précis !
Le lien vers la demande de fusion ne fonctionne pas / plus mais j’ai retrouvĂ© le commit via le graph du repo.

Je vois deux solutions :

  • (a) j’essaie d’appliquer les changements avec un patch sur mon instance dokos
  • (b) j’attend qu’une demande de fusion soit intĂ©grĂ©e Ă  la branche master de dodock

@chdecultot un avis ou un timing pour intégrer le commit de @corentin ?

Belle fin de journée,

Bonjour @guillaume.augais,

Je n’avais pas vu que les MR n’étaient pas accessibles Ă  tous sur Gitlab, c’est corrigĂ©.

J’ai fait plusieurs tests avec des passerelles SMTP diffĂ©rentes et je n’ai pas rĂ©ussi Ă  reproduire le problĂšme d’encodage pour le Message-Id.
Du coup, serait-il possible de connaĂźtre la passerelle SMTP que vous utilisez ?

Je voudrais m’assurer que le problùme vient bien de l’encodage de Dokos et pas de la passerelle sous-jacente.

Part contre j’ai bien la mĂȘme erreur pour le sujet, donc il faut qu’on corrige.

J’essaye de m’en occuper cette semaine.

Bien reçu pour la visibilité des MR, merci.

La passerelle sous-jacente est Gandi et le DKIM est activĂ© sur la boite mail d’envoi qui est bonjour@atelierdusoleiletduvent.org.

Voici la configuration du domaine sous Dokos :

  • IMAP en SSL sur le port 993 (pas pertinent Ă  priori)
  • SMTP en SSL sans TLS sur le port 465

Si cela peut aider, voici le lien vers l’analyse mail-tester : Spam Test Result
L’email a Ă©tĂ© envoyĂ© depuis le bouton « Nouvel Email Â» du pied de page d’une commande client Dokos.

Suite Ă  vos remarques je constate que dans le « Journal des emails Â», le Message-Id semble formattĂ© correctement
 C’est Ă  dire avec les caractĂšres â€č < â€ș et â€č > â€ș au lieu de â€č =?utf-8?q?=3C â€ș et â€č =3E?= â€ș

=> Voir cet export du contenu source, extrait du champ « Message Â» aprĂšs sĂ©lection de l’email en question dans le « Journal des emails Â» : Re: Guillaume AUGAIS (#CMD-VT-2022-00087)

Je viens de faire plusieurs tests :

  • client Thunderbird / compte Gandi sans DKIM : pas de pb de Message-Id (mail-tester donne 9/10 car pas de DKIM)
  • client Thunderbird / compte Gandi avec DKIM : pas de pb de Message-Id (mail-tester donne 10/10)

Ce qui metterait hors de cause Gandi, ou l’activation du DKIM

J’ai aussi vĂ©rifiĂ© en modifiant la config SMTP Dokos : passer de 465 (avec SSL) Ă  587 (avec TLS) n’a rien changĂ© Ă  l’allure du â€č Message-Id â€ș reçu par mail-tester

Comparaison des en-tĂȘtes Dokos / Thunderbird

Avec un envoi de mail Dokos,
le formattage des en-tĂȘtes est le suivant :

Content-Type: multipart/mixed; boundary=« ===============8978091418656974850== Â»
MIME-Version: 1.0
Message-Id: 165402808017.3389.2808957330285164207.v2NW1ExiIU@www.atelierdusoleiletduvent.org
X-Original-From: Guillaume AUGAIS bonjour@atelierdusoleiletduvent.org
Subject: Re: Guillaume AUGAIS (#CMD-VT-2022-00087)
From: Atelier du Soleil et du Vent bonjour@atelierdusoleiletduvent.org
To: test-6gci2kqob@srv1.mail-tester.com
Date: Tue, 31 May 2022 20:14:43 -0000

Alors que via Thunderbird, j’ai :

Content-Type: multipart/alternative;
boundary=« ------------WolpAFcc0rwpqED1z0ClrKkw Â»
Message-ID: 16fe9764-36c5-9a01-0b3d-c14c427cafba@mondomainetest.fr
Date: Tue, 31 May 2022 22:37:27 +0200
MIME-Version: 1.0

Plusieurs remarques :

  • (1) le champs boundary est sur une ligne nouvelle dans Thunderbird
  • (2) l’en-tĂȘte â€č MIME-Version â€ș est intercalĂ©e entre â€č Content-Type â€ș et â€č Message-Id â€ș aprĂšs (mais cela ne devrait pas avoir d’incidence j’imagine)

Est-ce que (1) peut avoir une influence ?

En espérant que ces nouveaux éléments aident à mieux comprendre le problÚme.
Si vous voyez d’autres tests Ă  rĂ©aliser, dites-moi.

Bonjour @guillaume.augais,

AprĂšs analyse approfondie du problĂšme, nous allons publier ce soir un correctif pour changer la politique d’encodage des emails (merci @corentin). J’ai fait un certain nombre de tests sans constater de problĂšme Ă  la rĂ©ception.
Ca devrait rĂ©soudre le problĂšme d’encodage du sujet.

On ajoutera le header List-Unsubscribe dans la v3, parce qu’il y a quelques problĂšmes avec la longueur de l’URL qu’il faut rĂ©gler d’abord.

Par contre je n’ai pas rĂ©ussit Ă  reproduire le problĂšme du message-id, malgrĂ© tous mes tests. Mail-tester donne bien une note de 9/10 Ă  tous les emails de test (le 1/10 Ă©tant liĂ© Ă  l’absence de DKIM).

Merci beaucoup pour votre aide !

Bonjour,

J’ai finalement pris le temps de mettre à jour l’instance (v2.18.2 de dokos).
Je confirme que je n’observe plus le souci d’encodage du sujet.

En revanche, j’observe la mĂȘme chose avec l’erreur sur le Message-Id.

C’est dĂ©jĂ  un bon pas, un grand merci Ă  nouveau.
Je vais voir si cela suffit pour éviter la catégorisation en spam chez certains hébergeurs mails.

Belle semaine

1 « J'aime »

Bonsoir,

Ce message pourrait faire l’objet d’une nouvelle issue mais je soupçonne fortement une rĂ©gression


En effet, il semblerait que je ne peux plus utiliser frappe.sendmail() avec un sujet qui comporte un accent.

Depuis la console, ceci fonctionne

frappe.sendmail(
    recipients=['blabla@gmail.com'],
    subject="Message sans accent",
    message="Lien",
    as_markdown=False,
    delayed=False,
)

alors que ceci génÚre une erreur :

frappe.sendmail(
    recipients=['blabla@gmail.com'],
    subject="Message accentué",
    message="Lien",
    as_markdown=False,
    delayed=False,
)

Voici la trace :

In [7]: frappe.sendmail(
   ...:     recipients=['blabla@gmail.com'],
   ...:     subject="Message accentué",
   ...:     message="Lien",
   ...:     as_markdown=False,
   ...:     delayed=False,
   ...: )
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/dokos/apps/frappe/frappe/commands/utils.py in <module>
      4     message="Lien",
      5     as_markdown=False,
----> 6     delayed=False,
      7 )

~/dokos/apps/frappe/frappe/__init__.py in sendmail(recipients, sender, subject, message, as_markdown, delayed, reference_doctype, reference_name, unsubscribe_method, unsubscribe_params, unsubscribe_message, add_unsubscribe_link, attachments, content, doctype, name, reply_to, queue_separately, cc, bcc, message_id, in_reply_to, send_after, expose_recipients, send_priority, communication, retry, now, read_receipt, is_notification, inline_images, template, args, header, print_letterhead, with_container)
    531                 send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, queue_separately=queue_separately,
    532                 communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
--> 533                 inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container)
    534 
    535 whitelisted = []

~/dokos/apps/frappe/frappe/email/queue.py in send(recipients, sender, subject, message, text_content, reference_doctype, reference_name, unsubscribe_method, unsubscribe_params, unsubscribe_message, attachments, reply_to, cc, bcc, message_id, in_reply_to, send_after, expose_recipients, send_priority, communication, now, read_receipt, queue_separately, is_notification, add_unsubscribe_link, inline_images, header, print_letterhead, with_container)
    163                 header=header,
    164                 now=now,
--> 165                 print_letterhead=print_letterhead)
    166 
    167 

~/dokos/apps/frappe/frappe/email/queue.py in add(recipients, sender, subject, **kwargs)
    184                         frappe.db.commit()
    185         else:
--> 186                 email_queue = get_email_queue(recipients, sender, subject, **kwargs)
    187                 if kwargs.get('now'):
    188                         send_one(email_queue.name, now=True)

~/dokos/apps/frappe/frappe/email/queue.py in get_email_queue(recipients, sender, subject, **kwargs)
    228 
    229                 e.message_id = mail.msg_root["Message-Id"].strip(" <>")
--> 230                 e.message = cstr(mail.as_string())
    231                 e.sender = mail.sender
    232 

~/dokos/apps/frappe/frappe/email/email_body.py in as_string(self)
    246                 self.validate()
    247                 self.make()
--> 248                 return self.msg_root.as_string(policy=policy.SMTP)
    249 
    250 def get_formatted_html(subject, message, footer=None, print_html=None,

/usr/lib/python3.7/email/message.py in as_string(self, unixfrom, maxheaderlen, policy)
    156                       maxheaderlen=maxheaderlen,
    157                       policy=policy)
--> 158         g.flatten(self, unixfrom=unixfrom)
    159         return fp.getvalue()
    160 

/usr/lib/python3.7/email/generator.py in flatten(self, msg, unixfrom, linesep)
    114                     ufrom = 'From nobody ' + time.ctime(time.time())
    115                 self.write(ufrom + self._NL)
--> 116             self._write(msg)
    117         finally:
    118             self.policy = old_gen_policy

/usr/lib/python3.7/email/generator.py in _write(self, msg)
    193         meth = getattr(msg, '_write_headers', None)
    194         if meth is None:
--> 195             self._write_headers(msg)
    196         else:
    197             meth(self)

/usr/lib/python3.7/email/generator.py in _write_headers(self, msg)
    220     def _write_headers(self, msg):
    221         for h, v in msg.raw_items():
--> 222             self.write(self.policy.fold(h, v))
    223         # A blank line always separates headers from body
    224         self.write(self._NL)

/usr/lib/python3.7/email/policy.py in fold(self, name, value)
    181 
    182         """
--> 183         return self._fold(name, value, refold_binary=True)
    184 
    185     def fold_binary(self, name, value):

/usr/lib/python3.7/email/policy.py in _fold(self, name, value, refold_binary)
    203     def _fold(self, name, value, refold_binary=False):
    204         if hasattr(value, 'name'):
--> 205             return value.fold(policy=self)
    206         maxlen = self.max_line_length if self.max_line_length else float('inf')
    207         lines = value.splitlines()

/usr/lib/python3.7/email/headerregistry.py in fold(self, policy)
    256                 parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]))
    257         header.append(self._parse_tree)
--> 258         return header.fold(policy=policy)
    259 
    260 

/usr/lib/python3.7/email/_header_value_parser.py in fold(self, policy)
    142 
    143     def fold(self, *, policy):
--> 144         return _refold_parse_tree(self, policy=policy)
    145 
    146     def pprint(self, indent=''):

/usr/lib/python3.7/email/_header_value_parser.py in _refold_parse_tree(parse_tree, policy)
   2650                 # combining it with previously encoded words if allowed.
   2651                 last_ew = _fold_as_ew(tstr, lines, maxlen, last_ew,
-> 2652                                       part.ew_combine_allowed, charset)
   2653             want_encoding = False
   2654             continue

/usr/lib/python3.7/email/_header_value_parser.py in _fold_as_ew(to_encode, lines, maxlen, last_ew, ew_combine_allowed, charset)
   2727             # XXX We'll get an infinite loop here if maxlen is <= 7
   2728             continue
-> 2729         first_part = to_encode[:text_space]
   2730         ew = _ew.encode(first_part, charset=encode_as)
   2731         excess = len(ew) - remaining_space

TypeError: slice indices must be integers or None or have an __index__ method

Dans le fichier apps/frappe/frappe/email/email_body.py, la ligne 248 est appelée.
Ligne modifiée dans ce commit : fix: Change encoding policy to correctly encode subject in header (67842c03) · Commits · atelierdusoleiletudvent / Dodock · GitLab

Et commit qui a été effectué suite aux échanges ci-dessus


Pouvez-vous reproduire le bug ?

Bonjour @guillaume.augais,

Je viens de faire un test depuis la console sur un serveur de production et je n’ai pas eu d’erreur.
L’email est bien parti avec le sujet encodĂ©:
image

A la réception:
image

Par contre je remarque que nos serveurs de production sont sur Python 3.8 alors que vous ĂȘtes sur 3.7, ce qui pourrait expliquer que je ne peux pas le reproduire.
D’aprĂšs la trace d’erreur, la librairie Email de python n’est pas Ă  jour, il y a eu une correction de bug justement sur ces lignes qui est disponible sur Python3.7 (bpo-33529: Fix infinite loop in email header encoding by wojcikk2903 · Pull Request #12020 · python/cpython · GitHub)
Pouvez-vous essayer de mettre à jour Python3.7 et voir si ça résoud le problÚme ?

Merci et bonne journée!

Bonjour,

J’ai mis Ă  jour vers python 3.8.2 aprĂšs rĂ©installation de bench et dokos sur mon serveur qui tourne sous Debian 10.

Et je confirme Ă©galement : plus aucun souci avec le message ayant un sujet comportant un accent.
Et autre bonne nouvelle : le problĂšme de formattage du message-id semble avoir disparu.

J’obtiens donc 10/10 avec mail-tester. Hourra

En revanche j’ai du contourner quelques problĂšmes ou installer manuellement certaines dĂ©pendences python : le script ansible a eu du mal sous Debian 10 + python 3.8.2.

Pour info, j’avais gardĂ© le python 3.7 du systĂšme et j’ai rajoutĂ© une ‘alternative’ 3.8.2, que j’ai configurĂ©e comme par dĂ©faut avec update-alternatives.

Si j’ai un peu de temps et que l’occasion se prĂ©sente j’essaierai de repartir Ă  zĂ©ro sur un nouveau serveur avec debian 10 + python 3.8 et voir si j’ai les mĂȘmes soucis ou non.

2 « J'aime »

Bonjour @guillaume.augais,

Merci pour ce retour !
Je pense qu’ajouter une alternative est une bonne option. La version 3 nĂ©cessitera python 3.10 a minima pour fonctionner, donc il faudra ajouter une alternative supplĂ©mentaire.
Il y a une commande bench migrate-env qui permet de migrer un bench vers une nouvelle version de python disponible sur le systĂšme. On va la documenter pour le passage Ă  la version 3.

Pour info, les minima requis pour la version 3 seront:

  • Python 3.10
  • Nodejs 14 (nodejs 16 est possible aussi, mais pas encore compatible avec nodejs 18)
1 « J'aime »