2025/09/22(月)必ず受信できるメーリングリストの運用方法(From書換)
メーリングリストの受信トラブルを解決する
メーリングリスト運用、特にsubjectを変更するMLは、「受信できない!」「迷惑メールに入りすぎる!」などのトラブルが発生しがちです。ML(メーリングリスト)の受信確率を上げるには、以下の運用が一番です。
後半では、メーリングリストでの返信先問題(reply-to)も取り上げます。
SPFを活用する方式
(ML側)1.SPF設定---------必須
2.ヘッダFROM書換--必須(MLのドメインに書き換える)
3. reply-to:に投稿者とMLの両方をいれるようにする。
これで、SPFはPassします。
4.DMARC設定-------強い推奨(ただし当面はp=none設定)
5.ARC導入---------推奨
MLドメインのDMARCを rejectに設定したとしても、Fromが書き換わっているので、DMARCアライメントもpassしますが、運用当初はnoneで様子をみたほうがよいと思います。
※DMARCアライメントとは、ヘッダFromとSPFで認証されたreturn-path(=最終的に有効なMAIL FROM/envelope-from)が一致していること。
仮に、投稿者のDMARCがrejctに設定されていたとしても、FromがMLドメインに書き換わっているので、DMARCアライメントは関係がなくなります。
DKIMを活用する方式
(ML側)1. DKIM設定---------必須(MLが配送するメールにDKIMが必要です。)
2. +ヘッダFROM書換--必須(MLのドメインに書き換える)
3. reply-to:に投稿者とMLの両方をいれるようにする。
これでDKIMはpassします。
4. DMARC設定--------強い推奨(ただし当面はp=none設定)
5.ARC導入----------推奨
MLドメインのDMARCを rejectに設定したとしても、Fromが書き換わっているので、DMARCアライメントもpassしますが、運用当初はnoneで様子をみたほうがよいと思います。
仮に、投稿者のDMARCがrejctに設定されていたとしても、FromがMLドメインに書き換わっているので、DMARCアライメントは関係がなくなります。
ARCについて
ARCは、メーリングリスト*1が投稿者からのメールを受信した際のSPF,DKIMの認証情報を、次のサーバー(MTA)に引き継ぐもので、ヘッダに記載されます。しかし、ARCは有効な仕組みではありますが、完璧ではなく、受信側サーバーが斟酌してくれるのを祈るしかありません。
その点、ヘッダFROM書換は、MLの場合、投稿者からではなく、FROMからの投稿ということになるので、エラーメールとなる確率がぐっと下がります。
さらにDMARCを設定している場合には、DMARCアライメントも通るので、さらにエラーメールとなる確率がぐっと下がります。
さくらインターネットMLでFromを書き換えてSPFalignmentを成功させる・メールセキュリティ覚書(SPF,DKIM,DMARC)を参照。
ヘッダFrom書換による影響
上記のとおり、MLの運用においては、From書換が最強です。ヘッダFrom書換のデメリットとして、メール受信者のユーザーエクスペリエンスが低下する(Fromヘッダが投稿者ではなくMLドメインとなる)ということが度々指摘されます。
From: taro_nippon@example.jp → mlname@mldomain.jp となるということです。
しかし、これもメールアドレス部分ではなく、「表示名」部分を工夫することで、一定の対応が可能です。
From: 日本太郎<taro_nippon@example.jp> → 日本太郎 via ML<mlname@mldomain.jp> や
From: taro_nippon@example.jp → taro_nippon via ML<mlname@mldomain.jp>
となるということです。※表示名は他にも工夫の余地があります。
MLとはそういうものだ、という認識さえ登録メンバーに意識付けすれば*2、特に混乱はないように考えています。
MLへの返信が、適切に実施されるための要件
返信時の挙動
一般的なMLでは、reply-toヘッダはMLアドレスが指定されることが多いです(fmlのデフォルトはMLアドレス、Mailmanのデフォルトはreply-toなし)。すると、以下の問題が生じます。
ML(メーリングリスト)メンバーではない外部アドレスからの投稿の場合、reply-toがMLアドレスだけとなり、そのメールに返信(全員返信含む)すると、MLにしか返信されないので、当該外部の投稿者に返信されない。これはFrom書換しなくても生じる問題ですが、From書換だけをしても残る問題です。
よって、MLは、reply-to の設定に関し、以下の挙動が望まれるところです(下記A,Bの両方)。
A. MK(メーリングリスト)外部からの投稿に関しては、 reply-to に、①MLのメールアドレスのほか、②当該外部投稿者のメールアドレスの2つをセットする。Aは、外部投稿者への返信が漏れてしまうことを防ぎます。
B. MK内部(MLの登録メンバー)からの投稿に関しては、reply-to に、①MLのメールアドレスのみをセットする。
Aを内部投稿者にも貫くと、reply-toや宛先にMLメンバーのアドレスが次々と継ぎ足されることとなり、メールの宛先欄が混乱することとなるのでBが必要となります。
この点を改善しようとしたのが、メーリングリスト改善/さくらインターネット/Reply-toヘッダの記事です。
もうひとつのバリュエーションも上記の記事に加えました。
メーリングリストメンバーからの投稿については、reply-toの設定が元メールにあればそれを生かし、なければreply-toに、MLアドレスを設定する。
メンバー以外の外部からの投稿については、reply-toの設定があればそのまま生かし、reply-to設定がなければ、当該外部の方(Fromヘッダにあるアドレス)をreply-toに設定する。
最強のメーリングリスト運用方法
- ヘッダFrom書換
- Reply-toヘッダの適切な処理
これを実務記事にしたのが、さくらインターネットMLでFromを書き換えてSPFalignmentを成功させるの記事です。
ML登録者が気を付けること
- MLからのメールを単純転送して、他のドメイン(例:Gmail)で受け取ってはいけません。せっかくのSPFが崩れます。管理者に最終的に受け取るアドレスを登録してもらいましょう。
- Gmailに転送したいなら、単純転送ではなく、POP/IMAPアクセスにしましょう。
2025/09/20(土)さくらインターネットMLでFromを書き換えてSPFalignmentを成功させる
このページの簡便法は、間便法:Gmailにも届くメーリングリスト-さくらインターネット をご参照ください。
さくらインターネットのメーリングリストでFromを書き換える
メーリングリスト改善/さくらインターネット/Reply-toヘッダにて、reply-toの問題は解決しました。続いて、DMARCを通させるためにFrom書換をしてみましょう。
google groups,Microsoft365などは、From書換をしています。さくらインターネットもコントロールパネルで変更できるようにしてほしいものですが、執筆時点では対応していません。
Fromを書き換えないことには、DMARCのアライメントが通りません(SPFアライメント(alignment)は、ヘッダFROMとMLの発信envelope-from(return-path)の一致をみます)。
ここでは、From 投稿者の表示名_via_ML <MLアドレス> と変更する方法を紹介します。これで SPFアライメント は通ることとなります(ヘッダFromとenvelope-from(return-path)が一致)。
もし、投稿者の表示名が存在しない場合(=メールアドレスのみの場合)は、投稿者のアドレスの@より左の部分だけを表示名として取り込みます。
DKIMはMLが件名等を書き換えるため、壊れたままとなるので、DKIMアライメントに行き着く前に失敗します。しかし、Gmail は、SPFアライメントか,DKIMアライメントが通れば受信してくれるので、大丈夫です(執筆時点)。
※さくらインターネットは、MLにおいてDKIMを再署名する機能は提供してない。
設定の仕方は、fmlにスクリプトを組み込む方法 をご参照ください。
SUFFIXにつける追加文字(SUFFIX)は自由に変更できます。日本語もOK。
スクリプトを作成するファイル(CF)の文字コードは必ず確認してください。
my $DISPLAY_NAME_SUFFIX = '_via_ML';出力は、UTF-8 でのMIMEエンコーディングをデフォルトとしていますが、ISO_2022_JPでエンコードしたい場合には、下記のように変更してください。
my $CF_FILE_ENCODING = 'shiftjis'; # Change this if CF file uses different encoding
my $OUTPUT_MIME_ENCODING = 'ISO-2022-JP';以下スクリプト本体
$START_HOOK = q{
# ========================================
# Configuration Section
# ========================================
# Suffix to append to display names (configurable)
# Change this value to customize the suffix
my $DISPLAY_NAME_SUFFIX = '_via_ML';
# Character encoding of this CF file (for Japanese suffix support)
# Options: 'utf-8', 'shiftjis', 'euc-jp', 'iso-2022-jp'
my $CF_FILE_ENCODING = 'shiftjis'; # Change this if CF file uses different encoding
# Output MIME encoding for From header
# Options: 'UTF-8' (international standard), 'ISO-2022-JP' (Japanese traditional)
my $OUTPUT_MIME_ENCODING = 'UTF-8'; # Change to 'ISO-2022-JP' for legacy systems
# Convert suffix to UTF-8 if it contains non-ASCII characters
if ($DISPLAY_NAME_SUFFIX =~ /[\x80-\xFF]/) {
# Suffix contains non-ASCII, decode from CF file encoding
if ($CF_FILE_ENCODING ne 'utf-8') {
my $temp = decode($CF_FILE_ENCODING, $DISPLAY_NAME_SUFFIX);
if (defined $temp) {
$DISPLAY_NAME_SUFFIX = $temp;
}
}
}
# ========================================
# Reply-To Header Processing
# ========================================
# Check if sender is a mailing list member
if (&MailListMemberP($From_address)) {
# Member: Set Reply-To to mailing list if not already set
unless (&GET_HEADER_FIELD_VALUE('reply-to')) {
&DEFINE_FIELD_FORCED("reply-to", $MAIL_LIST);
}
}
else {
# Non-member: Add both original address and ML to Reply-To
if (&GET_HEADER_FIELD_VALUE('reply-to')) {
# Append ML to existing Reply-To
&DEFINE_FIELD_FORCED("reply-to", &GET_HEADER_FIELD_VALUE('reply-to') . "," . $MAIL_LIST);
} else {
# Create new Reply-To with sender and ML
&DEFINE_FIELD_FORCED("reply-to", $From_address . "," . $MAIL_LIST);
}
}
# ========================================
# From Header Rewrite Processing
# ========================================
# Load required modules
use Encode qw(decode encode);
use Encode::MIME::Header;
# Get and save original From header
my $orig_from = &GET_HEADER_FIELD_VALUE('from');
&DEFINE_FIELD_FORCED('x-original-from', $orig_from);
# Extract display name and email address from From header
my ($display_name, $email_address) = extract_from_parts($orig_from);
# Build new From header
my $new_from;
if ($display_name) {
# Add configured suffix to display name
$display_name .= $DISPLAY_NAME_SUFFIX;
# Check if string contains non-ASCII characters
if ($display_name =~ /[^\x00-\x7F]/) {
# Ensure proper UTF-8 internal representation
utf8::decode($display_name) unless utf8::is_utf8($display_name);
# Encode entire display name as single MIME-Header block
# This creates a proper RFC 2047 encoded-word
my $encoded_name;
if ($OUTPUT_MIME_ENCODING eq 'UTF-8') {
# Use UTF-8 encoding (modern standard)
$encoded_name = encode('MIME-Header', $display_name);
} else {
# Use ISO-2022-JP encoding (Japanese traditional)
$encoded_name = encode('MIME-Header-ISO_2022_JP', $display_name);
}
$new_from = $encoded_name . ' <' . $MAIL_LIST . '>';
} else {
# ASCII only, use with quotes
$display_name =~ s/"/\\"/g; # Escape existing quotes
$new_from = '"' . $display_name . '" <' . $MAIL_LIST . '>';
}
} else {
# If no display name found, use local part of email address
my $local_part = $email_address || 'unknown';
$local_part =~ s/@.*$//;
$new_from = '"' . $local_part . $DISPLAY_NAME_SUFFIX . '" <' . $MAIL_LIST . '>';
}
# Set new From header
&DEFINE_FIELD_FORCED('from', $new_from);
# ========================================
# Function: Parse From Header
# ========================================
sub extract_from_parts {
my ($from_header) = @_;
my $display_name = '';
my $email_address = '';
# Return empty if From header is empty
return ('', '') unless $from_header;
# Store original for processing
my $decoded_from = $from_header;
# Check if header contains MIME encoding
if ($from_header =~ /=\?[\w\-]+\?[BQbq]\?/) {
# MIME encoded header detected - decode it
my $temp = decode('MIME-Header', $from_header);
if (defined $temp) {
$decoded_from = $temp;
# Ensure UTF-8 flag is set properly
utf8::decode($decoded_from) unless utf8::is_utf8($decoded_from);
}
} else {
# Not MIME encoded - should be ASCII only per RFC 5322
# Log warning if non-ASCII bytes detected (RFC violation)
if ($decoded_from =~ /[\x80-\xFF]/) {
&LOG("warning: Non-ASCII bytes in non-MIME header (RFC violation): $from_header");
# Special case: ISO-2022-JP with escape sequences
# Some older systems might pass this through
if ($decoded_from =~ /\x1b\$B/) {
my $temp = decode('iso-2022-jp', $decoded_from);
if (defined $temp) {
$decoded_from = $temp;
}
}
# For other non-ASCII cases, continue with original
# (will likely result in using email local part as fallback)
}
}
# Parse From header structure
# Remove any leading/trailing whitespace
$decoded_from =~ s/^\s+//;
$decoded_from =~ s/\s+$//;
# Pattern 1: "Display Name" <email@example.com>
if ($decoded_from =~ /^"([^"]*(?:\\.[^"]*)*)"\s*<([^>]+)>/) {
$display_name = $1;
$email_address = $2;
# Unescape quotes in display name
$display_name =~ s/\\"/"/g;
}
# Pattern 2: Display Name <email@example.com> (no quotes)
elsif ($decoded_from =~ /^([^<]+?)\s*<([^>]+)>/) {
$display_name = $1;
$email_address = $2;
# Clean up display name
$display_name =~ s/^\s+//;
$display_name =~ s/\s+$//;
}
# Pattern 3: <email@example.com> (no display name)
elsif ($decoded_from =~ /<([^>]+)>/) {
$email_address = $1;
$display_name = '';
}
# Pattern 4: email@example.com (bare email)
elsif ($decoded_from =~ /([^\s<>]+@[^\s<>]+)/) {
$email_address = $1;
$display_name = '';
}
else {
# Unable to parse
&LOG("warning: Unable to parse From header: $decoded_from");
# Try to extract email as last resort
if ($decoded_from =~ /([^\s<>]+@[^\s<>]+)/) {
$email_address = $1;
}
}
# Clean up extracted values
$email_address =~ s/^\s+//;
$email_address =~ s/\s+$//;
if ($display_name) {
# Remove extra whitespace
$display_name =~ s/^\s+//;
$display_name =~ s/\s+$//;
$display_name =~ s/\s+/ /g;
# IMPORTANT: Remove surrounding quotes that may have been included from MIME decode
# This prevents double-quoting when re-encoding
$display_name =~ s/^"//;
$display_name =~ s/"$//;
}
return ($display_name, $email_address);
}
# ========================================
# End of Hook Processing
# ========================================
};
2024/08/16(金)メーリングリスト改善/さくらインターネット/Reply-toヘッダ
さくらインターネットのメーリングリストReply-toを改修する
さくらインターネットのML(mailing list メーリングリスト)の困るところ
チームで仕事しているとML(メーリングリスト)をよく使います。メーリングリストの登録メンバーだけでコミュニケーションとるにはとても便利ですが、「MLメンバー以外の外部からMLにポストがあり、それに返信するとき」に困った問題が起きます。
登録されていない外部からのメールにreply-to ヘッダが設定されていない場合(大抵設定されていない)、MLのアドレスがreply-toに設定されます。
すると、多くのメーラーにおいて*1「全員返信」をすると、肝心のFromヘッダーの人(外部の人)が宛先に入らず、そのFromの人に返信したつもりなのに、宛先入っていない!ということが起きます。
ということで、それの回避策です。
なお、さくらインターネットのレンタルサーバー(私が使っているもの)は、MLのシステムとしてfmlを使っています。
fmlは、日本人開発者の深町 賢一さんが作成したメーリングリストソフトウエアであり、世界中で使われています。
https://www.fml.org/software/fml/
fmlバイブル: fml 4.0対応
不遜にも改修などといっていますが、ただの設定変更です。fmlは完成度の高い、素晴らしいソフトウエアであり、さくらインターネットが使いこなせていないだけです。
さくらインターネットのメーリングリストの改修
fmlの公式サイトやfmlバイブル: fml 4.0対応にたくさんのヒントがありますが、ここでは以下の設定とします。
メーリングリストメンバーからの投稿については、reply-toの設定が元メールにあればそれを生かし、なければreply-toに、MLアドレスを設定する。
メンバー以外の外部からの投稿については、reply-toの設定があれば、当該reply-toに、MLアドレスも加え、reply-to設定がなければ、当該外部の方(Fromヘッダにあるアドレス)とMLアドレスをreply-toに設定する。
$START_HOOK =q#
if (&MailListMemberP($From_address)) {
unless (&GET_HEADER_FIELD_VALUE('reply-to')) {
&DEFINE_FIELD_FORCED("reply-to", $MAIL_LIST);
}
}
else {
if (&GET_HEADER_FIELD_VALUE('reply-to')) {
&DEFINE_FIELD_FORCED("reply-to", &GET_HEADER_FIELD_VALUE('reply-to').",".$MAIL_LIST);
}else{
&DEFINE_FIELD_FORCED("reply-to", $From_address.",".$MAIL_LIST);
}
}
#;
インデントをTABでやっているので、WEBの表示上、インデントがうまく表現できていませんがご容赦ください。スペースでインデントしようとも思ったのですが、何かとトラブルのもとになるので、上記のようにしています。スクリプトを組み込む方法
ここからは、UNIX(Linux)の知識が必要なので、尻込みする人がいるかもですが、たいした操作はしないので、手順とおりやれば大丈夫です。まず該当のメーリングリストのcfファイルをダウンロードして加工します。
cfファイルの場所は以下のとおりで、ftpアプリ等でダウンロードしましょう。
/home/<アカウント名>/fml/spool/ml/<メーリングリスト名>
cfファイルに拡張子はついていないので、テキストエディタで無理矢理開きます。
cfファイルの最後のほうに、以下の記述があります。
# YOU CAN EDIT MANUALLY AFTER HERE.この直後に、前掲のスクリプトをコピペします。そしてアップロードして元の場所に戻しましょう。
その後、SSHクライアント*2で、以下のコマンドを順に実行します。
cd fml/spool/ml/<mailing list><mailing list>のところは、メーリングリストの@から左の部分です。
make config.ph
cdは、change directory のコマンドであり、そのディレクトリ(フォルダ)の場所まで移動するコマンドです。
うまく移動できなければ、lsコマンド*3も利用して、cfファイルがある場所まで到着してください。
そのうえで、
make config.phとします。これでfmlにさきほどのスクリプトが組み込まれました。
エラーがでていなければOKです。
動作確認をしてみましょう。
makeしてから反映までしばらく時間がかかることもあるようです。
参考にさせていただいたサイト
さくらインターネットでメーリングリストを開設したら最初にするべきことさくらメーリングリストにおけるreply-to設定 on fml
もうひとつのパターン
メーリングリストメンバーからの投稿については、reply-toの設定が元メールにあればそれを生かし、なければreply-toに、MLアドレスを設定する。
メンバー以外の外部からの投稿については、reply-toの設定があればそのまま生かし、reply-to設定がなければ、当該外部の方(Fromヘッダにあるアドレス)をreply-toに設定する。
$START_HOOK =q#
if (&MailListMemberP($From_address)) {
unless (&GET_HEADER_FIELD_VALUE('reply-to')) {
&DEFINE_FIELD_FORCED("reply-to", $MAIL_LIST);
}
}
else {
unless (&GET_HEADER_FIELD_VALUE('reply-to')) {
&DEFINE_FIELD_FORCED("reply-to", $From_address);
}
}
#;