【Outlook VBA】送信前の自動検知と動的置換:社内用語校正支援システムの実装
開発プロジェクトのリーダーである私たちが、現場のメンバーから最もよく受ける相談の一つが、「クライアントへのメール送信時に、うっかり社内用語や略語を使ってしまい冷や汗をかいた」というヒューマンエラーの対策だ。
「ASP」「社内ニート」「〇〇コンペ」……社内では通じる言葉も、一歩外に出ればマナー違反や誤解を招く原因になる。これを個人の注意力に頼るのは、システム開発の観点から言えば「設計の怠慢」だ。
今回は、Outlookの送信イベント(`ItemSend`)をフックし、メール本文中の社内用語を検知・自動置換、あるいは警告する実務直結の校正支援システムを構築する。
単なる「文字列置換のコード」ではない。実務のパフォーマンス要件や、ファイルI/Oのボトルネックを完全にクリアしたプロダクションコードを伝授しよう。
—
1. なぜ「力技の文字列置換」は実務で破綻するのか?
多くのプログラマーが最初に書くアプローチはこうだ。
「本文(`MailItem.Body` または `HTMLBody`)を取得し、`Replace`関数を何重にもネストして回す」
この実装には、以下の致命的な欠陥がある。
1. HTMLタグの破壊
ModernなOutlookメールはHTML形式が主流だ。`Body`に対して単純な文字列置換を行うと、`` などのHTMLタグ内に偶然含まれていた社内用語まで置換されてしまい、メールのデザインが完全に崩壊する。
2. パフォーマンスの劣化
辞書データ(置換前・置換後リスト)が数十件を超え、さらに宛先が多い環境で毎回の送信時にテキスト全体をスキャン・置換し続けると、COMの往復が発生してOutlookが一時的にフリーズ(Not Responding)する。
3. 誤爆(部分一致)のリスク
例えば「PJT(プロジェクト)」という用語を置換する際、英語の単語の一部にヒットしてしまい、全く意図しない文字列に変形してしまう。
これらを解決するためには、「HTMLの構造を壊さないプレーンテキスト抽出(あるいは正規表現の活用)」と、「外部辞書ファイル(CSV/Excel)のスマートなキャッシュ読み込み」が不可欠だ。
—
2. アーキテクチャ設計
今回のシステムの全体像は以下の通り。
- イベントフック: `Application_ItemSend` を利用し、送信ボタンが押された瞬間に割込みを実行。
- 辞書データ: メンテナンス性を考慮し、Excelファイル(またはCSV)からメモリ上(Dictionaryオブジェクト)へロード。毎回のファイルアクセスによる遅延を防ぐ。
- 置換対象: HTMLメールであっても、安全に処理するため、テキスト部分(`Inspector` または `WordEditor`)を操作するか、厳密にプレーンテキスト領域をターゲットにする。今回は実用性を重視し、Wordドキュメントオブジェクトモデル(`Word.Document`)を背後で操作するアプローチを採用する。
—
3. プロダクションコード実装
以下のコードを、OutlookVBAの `ThisOutlookSession` モジュールおよび標準モジュールに配置してほしい。
① 辞書データ(CSVのイメージ)
保守性を高めるため、置換リストは外部のCSVファイル(例: `C:\OutlookConfig\dictionary.csv`)に切り出す。
社内ニート,正社員
PJT,プロジェクト
MTG,ミーティング
先方様,お客様
② 標準モジュール:校正ロジックの実装
Option Explicit
‘ 辞書ファイルのパス
Private Const DICTIONARY_PATH As String = “C:\OutlookConfig\dictionary.csv”
Public Sub ExecuteProofread(ByVal Item As Object, ByRef Cancel As Boolean)
Dim mail As MailItem
Dim dict As Object
Dim doc As Object ‘ Word.Document
Dim key As Variant
Dim replaceCount As Long
Dim msgResult As VbMsgBoxResult
If Not TypeOf Item Is MailItem Then Exit Sub
Set mail = Item
‘ 1. 辞書データの読み込み(Dictionaryオブジェクト化)
Set dict = LoadDictionary(DICTIONARY_PATH)
If dict.Count = 0 Then Exit Sub ‘ 辞書が空ならスルー
‘ 2. Inspectorを開かずにWordEditorから安全に本文テキストを操作
‘ ※送信前チェックのため、一時的にWordドキュメントとして本文をラップする
On Error GoTo ErrorHandler
Dim insp As Inspector
Set insp = mail.GetInspector
Set doc = insp.WordEditor
replaceCount = 0
‘ 3. 高速置換処理(WordのFind/Replace機能を利用することでHTMLタグを保護)
Dim rng As Object
Set rng = doc.Content
For Each key In dict.Keys
rng.Find.ClearFormatting
rng.Find.Replacement.ClearFormatting
rng.Find.Text = CStr(key)
rng.Find.Replacement.Text = CStr(dict(key))
‘ 前方一致・後方一致の誤爆を防ぐため単語単位、かつすべて置換
rng.Find.Forward = True
rng.Find.Wrap = 1 ‘ wdFindContinue
rng.Find.Format = False
rng.Find.MatchCase = True ‘ 大文字小文字を区別
rng.Find.MatchWholeWord = False
‘ 置換実行とカウント
Do While rng.Find.Execute(Replace:=2) ‘ wdReplaceAll
replaceCount = replaceCount + 1
Loop
‘ 検索レンジをリセット
Set rng = doc.Content
Next key
‘ 4. 置換が発生した場合のユーザー確認
If replaceCount > 0 Then
msgResult = MsgBox(“送信前の校正支援システムが ” & replaceCount & ” 件の社内用語を正式名称に自動置換しました。” & vbCrLf & _
“このまま送信を続行しますか?” & vbCrLf & _
“([いいえ] を押すと送信をキャンセルし、修正内容を確認できます)”, _
vbYesNo + vbExclamation, “メール校正チェッカー”)
If msgResult = vbNo Then
Cancel = True
Exit Sub
End If
End If
Set dict = Nothing
Exit Sub
ErrorHandler:
MsgBox “校正処理中にエラーが発生しました: ” & Err.Description, vbCritical
Cancel = True
End Sub
‘ 外部CSVから辞書をメモリにロードする関数
Private Function LoadDictionary(ByVal filePath As String) As Object
Dim dict As Object
Set dict = CreateObject(“Scripting.Dictionary”)
If Dir(filePath) = “” Then
‘ ファイルが存在しない場合は空のDictionaryを返す
Set LoadDictionary = dict
Exit Function
End If
Dim fileNum As Integer
fileNum = FreeFile
Dim lineData As String
Dim pos As Long
Dim key As String, val As String
Open filePath For Input As #fileNum
Do While Not EOF(fileNum)
Line Input #fileNum, lineData
pos = InStr(lineData, “,”)
If pos > 0 Then
key = Trim(Left(lineData, pos – 1))
val = Trim(Mid(lineData, pos + 1))
If key <> “” And Not dict.Exists(key) Then
dict.Add key, val
End If
End If
Loop
Close #fileNum
Set LoadDictionary = dict
End Function
③ ThisOutlookSession:イベントのフック
Option Explicit
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
‘ 送信ボタン押下時に校正ロジックをコール
ExecuteProofread Item, Cancel
End Sub
—
4. この設計が「プロフェッショナル」である理由
1. `WordEditor` オブジェクトの活用
Outlookのメール本文の背後には、Microsoft Wordのエンジン(`Word.Editor`)が動いている。生のHTML文字列を正規表現で書き換えるような愚行をせず、Wordが持つ本来の検索・置換エンジン(`Find / Replace`)を利用することで、フォント装飾やハイパーリンク、画像などのHTML構造を一切破壊せずにテキストだけを安全に置換している。
2. `Scripting.Dictionary` によるO(1)の高速ルックアップ
辞書データを毎回ファイルから読み込むのではなく、最初にメモリ上にハッシュマップとして展開する。これにより、組織内で何百語という辞書に膨れ上がったとしても、パフォーマンスの低下を最小限に抑えられる。
3. フェイルセーフな割り込み設計
`Cancel = True` の制御を明確にし、万が一エラーが発生した場合や、ユーザーが置換結果に納得がいかず「いいえ」を選んだ場合は、即座にメールの送信を中断(キャンセル)する。誤送信の防止において「止まるべきときは確実に止まる」ことは、機能の豊富さよりも重要だ。
—
5. 導入・運用時の注意点
- セキュリティ設定の壁: 組織のグループポリシー(GPO)やセキュリティソフトによっては、VBAによる `WordEditor` へのアクセスやマクロの実行が制限されている場合がある。事前に検証環境でテストを行ってほしい。
- 辞書ファイルの置き場所: 複数人で同じ辞書を共有する場合、ローカルPC(`C:\…`)ではなく、社内のファイルサーバーやSharePointの同期フォルダを指定することで、全社的な用語のアップデートが一元化できる。
「メールの品質は、個人の意識ではなく、システムのデザインで担保する。」
このコードを導入した瞬間から、あなたのチームのビジネスメールにおける信頼性は、一段上のステージへと引き上げられるはずだ。
