マーケティングメールが迷惑メールになる?判定ツールを作成!
メールマーケティングキャンペーンを実施する際、最大の課題の一つは、メッセージが迷惑メールフォルダーではなく受信トレイに届くようにすることです。
この記事では、メールが迷惑メールとしてマークされるかどうか、そしてその理由を検証できるツールを作成します。このツールはAPI形式でオンラインにデプロイされ、ワークフローに統合できるよう設計します。
迷惑メール検証の秘密
Apache SpamAssassin は、Apache Software Foundationによって維持されているオープンソースの迷惑メール検出プラットフォームです。多くのメールクライアントやメールフィルタリングツールが、このツールを使用してメッセージを迷惑メールとして分類しています。
SpamAssassinは、多数のルール、ベイジアンフィルタリング、ネットワークテストを使用して、特定のメールに迷惑メール「スコア」を割り当てます。一般的には、スコアが5以上のメールは迷惑メールとしてフラグ付けされるリスクが高いとされています。
SpamAssassinのスコアリングは透明性が高く、詳細にドキュメント化されているため、メールのどの部分が高い迷惑メールスコアを引き起こしているのかを特定し、文章を改善することが可能です。
SpamAssassinを使ったメール検証方法
SpamAssassinはLinuxシステムで動作するよう設計されています。Linux OSを使用するか、Dockerコンテナを作成してインストールと実行を行う必要があります。
DebianまたはUbuntuシステムでは、以下のコマンドでSpamAssassinをインストールできます:
apt-get update && apt-get install -y spamassassin
sa-update
sa-update
コマンドを使用すると、SpamAssassinのルールを最新の状態に保つことができます。
インストール後、メールメッセージをSpamAssassinのコマンドラインツールにパイプ入力できます。出力には迷惑メールスコアや、どのルールがトリガーされたのかが説明された注釈付きのメールが含まれます。
使用例:
spamassassin -t < input_email.txt > results.txt
この後、results.txt
にはSpamAssassinのヘッダーとスコアが含まれる処理済みのメールが格納されます。例:
X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on 254.254.254.254
X-Spam-Level:
X-Spam-Status: No, score=0.2 required=5.0 tests=HTML_MESSAGE,
MIME_HTML_ONLY,MISSING_MID,NO_RECEIVED,
NO_RELAYS autolearn=no autolearn_force=no version=4.0.0
// ...
Content analysis details: (0.2 points, 5.0 required)
pts rule name description
---- ---------------------- --------------------------------------------------
0.1 MISSING_MID Missing Message-Id: header
-0.0 NO_RECEIVED Informational: message has no Received headers
-0.0 NO_RELAYS Informational: message was not relayed via SMTP
0.0 HTML_MESSAGE BODY: HTML included in message
0.1 MIME_HTML_ONLY BODY: Message only has text/html MIME parts
SpamAssassinをAPIとしてラップする
SpamAssassinはAPIとしてカプセル化することで最大の潜在能力を発揮します。この形式では柔軟性が向上し、さまざまなワークフローに統合可能です。
例えば、メールを送信する前に、コンテンツをSpamAssassin APIに送信して検証する仕組みを構築できます。迷惑メールの基準を満たしていないと判断された場合にのみ送信が許可されます。
以下のようなAPIを作成してみましょう。このAPIは次のメールフィールドを受け付けます:subject
、html_body
、text_body
。これらをSpamAssassinに渡し、検証結果を返します。
API例
from fastapi import FastAPI
from datetime import datetime, timezone
from email.utils import format_datetime
from pydantic import BaseModel
import subprocess
def extract_analysis_details(text):
lines = text.splitlines()
start_index = None
for i, line in enumerate(lines):
if line.strip().startswith("pts rule"):
start_index = i
break
if start_index is None:
print("No content analysis details found.")
return []
data_lines = lines[start_index+2:]
parsed_lines = []
for line in data_lines:
if line.strip() == "":
break
parsed_lines.append(line)
results = []
current_entry = None
split_line = lines[start_index+1]
pts_split, rule_split, *rest = split_line.strip().split(" ")
pts_start = 0
pts_end = pts_start + len(pts_split)
rule_start = pts_end + 1
rule_end = rule_start + len(rule_split)
desc_start = rule_end + 1
for line in parsed_lines:
pts_str = line[pts_start:pts_end].strip()
rule_name_str = line[rule_start:rule_end].strip()
description_str = line[desc_start:].strip()
if pts_str == "" and rule_name_str == "" and description_str:
if current_entry:
current_entry["description"] += " " + description_str
else:
current_entry = {
"pts": pts_str,
"rule_name": rule_name_str,
"description": description_str
}
results.append(current_entry)
return results
app = FastAPI()
class Email(BaseModel):
subject: str
html_body: str
text_body: str
@app.post("/spam_check")
def spam_check(email: Email):
# assemble the full email
message = f"""From: example@example.com
To: recipient@example.com
Subject: {email.subject}
Date: {format_datetime(datetime.now(timezone.utc))}
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="__SPAM_ASSASSIN_BOUNDARY__"
--__SPAM_ASSASSIN_BOUNDARY__
Content-Type: text/plain; charset="utf-8"
{email.text_body}
--__SPAM_ASSASSIN_BOUNDARY__
Content-Type: text/html; charset="utf-8"
{email.html_body}
--__SPAM_ASSASSIN_BOUNDARY__--"""
# Run SpamAssassin and capture the output directly
output = subprocess.run(["spamassassin", "-t"],
input=message.encode('utf-8'),
capture_output=True)
output_str = output.stdout.decode('utf-8', errors='replace')
details = extract_analysis_details(output_str)
return {"result": details}
上記のコードでは、extract_analysis_details
というヘルパー関数を定義し、結果レポートからスコアリング理由のみを抽出します。この関数をさらに改良し、特定のルールを結果から除外するなどの機能を追加することもできます。
以下の入力例を見てみましょう:
subject
Claim Your Prize
html_body
<h2>Claim Your Prize</h2>
<p>Dear Winner:</p>
<p>Click the link below to claim your prize.</p>
text_body
Claim Your Prize
Dear Winner:
Click the link below to claim your prize.
レスポンスの例:
[
{
"pts": "0.1",
"rule_name": "MISSING_MID",
"description": "Missing Message-Id: header"
},
{
"pts": "-0.0",
"rule_name": "NO_RECEIVED",
"description": "Informational: message has no Received headers"
},
{
"pts": "3.1",
"rule_name": "DEAR_WINNER",
"description": "BODY: Spam with generic salutation of \"dear winner\""
},
{
"pts": "-0.0",
"rule_name": "NO_RELAYS",
"description": "Informational: message was not relayed via SMTP"
},
{
"pts": "0.0",
"rule_name": "HTML_MESSAGE",
"description": "BODY: HTML included in message"
}
]
見てください!「Dear winner」は迷惑メールで一般的に使用される挨拶として検出されました。
APIをオンラインでデプロイする
SpamAssassinを実行するには、ソフトウェアがインストールされたLinux環境が必要です。従来では、EC2インスタンスやDigitalOceanのドロップレットをデプロイする必要がありましたが、これにはコストがかかり、特に低トラフィックの場合は非効率的です。
サーバーレスプラットフォームでは、SpamAssassinのようなシステムパッケージをインストールすることはできません。
Leapcell はこの問題を完全に解決します。
Leapcell を使用すれば、SpamAssassinのようなシステムパッケージをデプロイしつつ、サーバーレスの利便性を保つことができます。つまり、使用回数に応じてのみ料金が発生し、通常はコストを削減できます。
LeapcellでAPIをデプロイするのは非常に簡単です。環境を設定する必要はありません。Pythonイメージをデプロイし、「ビルドコマンド」フィールドを適切に設定するだけです。
デプロイが完了すると、迷惑メール検証のためのAPIが使用可能になります。このAPIが呼び出されるたびに、SpamAssassinが実行され、メールのスコアを算出して返します。
Discussion