trac.iniでsmtp_enabledをtrueにsmtp_fromかsmtp_replytoにアドレスを設定すれば、でチケットを登録したり更新した際に関係者にメールが送られる。
このメールについて、自分の趣味とは合わないな~と思っていること。

その1 - From:の表示名とアドレス

tracからのメールは以下のように「プロジェクトさん」から送られてきたことになる。
From: [trac]セクションのname値 <[notification]セクションのsmtp_from値>
一人でTODOやアイデア管理に使っている分には特に気にならないが、複数の利用者が1つのチケットに対してあれこれ変更や追記をすると、受信したメールの発信者が全て「プロジェクトさん」のため、メーラで整理しにくい。
そもそもSubjectにプロジェクト名が入っているので、メールの一覧表示における情報量に無駄があるなあ、と。
※ちなみに、tracから来たメールはプロジェクトごとに特定のフォルダに振り分けて、スレッド表示にしている。
  • From:フィールドの表示名を報告者の名前にする
    • といっても、on/offの制御はしたいところなので、trac.iniの[notification]セクションにuse_reporter_name=trueを設定すると、表示名が報告者の名前になるようにする。falseか未設定なら元の通りプロジェクト名が表示名になる。
    • 「報告者の名前」は、以下の優先順位で適用される
      1. 「ユーザ設定」で「名前」に記入した文字列
      2. 認証名(ログイン状態で「xxxxとしてログイン中」と出るアレ)
  • From:フィールドのメールアドレスを報告者のアドレスにする
    • 表示名と同様に、trac.iniの[notification]セクションにuse_reporter_email=trueを設定すると、Fromアドレスが報告者のアドレスになるようにする。falseか未設定なら元の通りプロジェクト名が表示名になる。
    • 結果的にFromの詐称を行うことになるので、smtpサーバによってはメール送信を蹴ってしまうかもしれない。(自ドメイン以外のメールアドレスがFrom:にあると蹴るケースなど)
    • smtp_fromに指定したアドレスはMAIL FROMとしてsmtpセッションで使われ、結果的にSender:の値になる。ウチで使っている環境に関して言うと、smtp_fromにsmtpサーバにとってローカルなメールアドレスを指定しておけば、From:が他ドメインでも送信してくれた。

その2 - From:の表示名とあて先の表示名をMIMEエンコードしない

特にanonymousで使っていて、「ユーザ設定」で日本語の名前を設定している場合、表示名部分をMIMEエンコードしないので文字化けしてしまう。先のエントリにも書いたようにウチの環境はanonymousでは使っておらず、認証名も英字のみとしているので、あまり問題ないのかも。
  • 送信先が「表示名 <アドレス>」の場合、表示名部分をMIMEエンコードする。判定はかなり適当。
  • 文字セットはSubjectを踏襲してUTF-8として、手抜きした。pythonでiso-2022-jpに変換するのって簡単なのかな。

その3 - Message-ID:の生成の仕方

プロジェクトのURLやチケットのIDを用い、ハッシュをとったもの+αにsmtp_fromの@以降を付与している。smtpサーバに外部のサーバを使用している場合にどうも気持ち悪い。
smtp_fromがxxxxxxx@example.comだと、
 Message-ID: <071.56dd0b824d3625a8ce2b492402b465db@example.com>
となる。
 Message-ID: <071.56dd0b824d3625a8ce2b492402b465db@tracサーバのFQDN>
としたい。
  • trac.iniの[notification]セクションで、use_fqdn4message_id=trueに設定した場合、Message-ID:の@以降をtracをホストしているサーバのFQDNにする。
  • falseか未設定なら従来通りsmtp_fromの@以降。

今回のパッチ

--- trac-0.9.5-ja-1/trac/Notify.py 2006-02-16 10:47:21.000000000 +0900
+++ trac-0.9.5-ja-1kai/trac/Notify.py 2006-06-25 00:07:56.000000000 +0900
@@ -20,9 +20,12 @@
 from trac.web.clearsilver import HDFWrapper
 from trac.web.main import populate_hdf
 
+import re
+import socket
 import md5
 import time
 import smtplib
+from email.Header import Header
 
 
 class Notify:
@@ -74,6 +77,8 @@
     server = None
     email_map = None
     template_name = None
+    reporter_name = ''
+    reporter_email = ''
 
     def __init__(self, env):
         Notify.__init__(self, env)
@@ -117,6 +122,17 @@
         if self.user_name:
             self.server.login(self.user_name, self.password)
 
+    def modifyNotification(self , authname , reporter_name_ , reporter_email):
+        if self.env.config.getbool('notification', 'use_reporter_name'):
+            if reporter_name_:
+                self.reporter_name = reporter_name_
+            elif authname:
+                self.reporter_name = authname
+
+        if self.env.config.getbool('notification', 'use_reporter_email'):
+            if reporter_email:
+                self.reporter_email = reporter_email
+
     def send(self, rcpt, mime_headers={}):
         from email.MIMEMultipart import MIMEMultipart
         from email.MIMEText import MIMEText
@@ -132,7 +148,18 @@
         msg['X-Trac-Project'] =  projname
         msg['X-URL'] =  self.config.get('project','url')
         msg['Subject'] = Header(self.subject, 'utf-8')
-        msg['From'] = '%s <%s>' % (projname, self.from_email)
+
+        display_name = projname
+        from_email_ = self.from_email
+        if self.env.config.getbool('notification', 'use_reporter_name') and \
+            self.reporter_name:
+            display_name = self.reporter_name
+
+        if self.env.config.getbool('notification', 'use_reporter_email') and \
+            self.reporter_email:
+            from_email_ = self.reporter_email
+        
+        msg['From'] = '%s <%s>' % (Header(display_name, 'utf-8'), from_email_)
         msg['Sender'] = self.from_email
         msg['Reply-To'] = self.replyto_email
         msg['To'] = rcpt
@@ -301,6 +328,11 @@
         emails = []
         for recipient in recipients:
             if recipient.find('@') >= 0:
+                p = re.compile('([^<]*)\s*(<.+>)')
+                m = p.match(recipient)
+                if m:
+                    recipient = '%s %s' % \
+                        (Header(m.group(1), 'utf-8'), m.group(2))
                 emails.append(recipient)
             elif self.email_map.has_key(recipient):
                 emails.append(self.email_map[recipient])
@@ -312,12 +344,20 @@
                 result.append(e)
         return result
 
+    def get_message_id_host(self):
+        if self.config.getbool('notification', 'use_fqdn4message_id'):
+            fqdn = socket.getfqdn()
+            if fqdn:
+                return fqdn
+
+        return self.from_email[self.from_email.find('@') + 1:]
+
     def get_message_id(self, rcpt, modtime=0):
         """Generate a predictable, but sufficiently unique message ID."""
         s = '%s.%08d.%d.%s' % (self.config.get('project', 'url'),
                                int(self.ticket.id), modtime, rcpt)
         dig = md5.new(s).hexdigest()
-        host = self.from_email[self.from_email.find('@') + 1:]
+        host = self.get_message_id_host()
         msgid = '<%03d.%s@%s>' % (len(s), dig, host)
         return msgid
 
url: archives/2006/06/trac-060625.html --- trac-0.9.5-ja-1/trac/ticket/web_ui.py 2006-02-16 10:59:33.000000000 +0900
+++ trac-0.9.5-ja-1kai/trac/ticket/web_ui.py 2006-06-24 22:28:52.000000000 +0900
@@ -139,6 +139,8 @@
         # Notify
         try:
             tn = TicketNotifyEmail(self.env)
+            tn.modifyNotification(req.authname, \
+                req.session.get('name', None), req.session.get('email', None))
             tn.notify(ticket, newticket=True)
         except Exception, e:
             self.log.exception("Failure sending notification on creation of "
@@ -347,6 +349,8 @@
 
         try:
             tn = TicketNotifyEmail(self.env)
+            tn.modifyNotification(req.authname, \
+                req.session.get('name', None), req.session.get('email', None))
             tn.notify(ticket, newticket=False, modtime=now)
         except Exception, e:
             self.log.exception("Failure sending notification on change to "

設定例

[notification]
always_notify_owner = true
smtp_always_cc =
smtp_password =
smtp_enabled = true
smtp_replyto = xxxxxxx@example.com
smtp_port = 25
always_notify_reporter = true
smtp_server = SMTPサーバ
smtp_from = xxxxxxx@example.com
smtp_user =
use_reporter_name = true
use_reporter_email = true
use_fqdn4message_id = true
あとは、SubjectかどこかでチケットのStatusがわかるといいんだけど、と思っているがとりあえずここまで。