【実務・中級編】【上級者向け】スレッドセーフを意識した非同期メール送信処理の設計と、エラーリカバリシステムの構築 – Outlook VBA解析バイブル

スポンサーリンク

【Outlook VBA上級編】スレッドセーフな非同期送信&自動リカバリ監視エンジンの設計

開発プロジェクトの現場で、こんな悪夢を見たことはないだろうか。

「大量の自動送信メール処理を実行中、ネットワークの瞬断やOutlookのフリーズが発生。送信トレイには未送信のメールが山積みになり、どれが送信済みでどれが未送信か分からない。顧客には二重送信のメールが飛び、上長からは大目玉を食らう……」

素人が書いたOutlook VBAのコードは、決まって `MailItem.Send` を直叩きしている。あれは地雷原を裸足で走るようなものだ。同期送信はOutlookのUIスレッドをブロックし、外部要因のエラーに対して無力である。

今回は、プロの現場に耐えうる「スレッドセーフを意識した非同期メール送信処理」と、「送信トレイを監視し障害から生還するリカバリエンジン」の全貌を伝授する。

1. なぜ従来のVBAメール送信は「バグの温床」なのか?

実務でVBAを使うエンジニアが陥る罠は主に3つある。

1. UIスレッドの占有とタイムアウト
`Send` メソッドはサーバーからの応答を同期的に待つ。大量送信や添付ファイル付きの処理では、Outlook自体が「応答なし」になり、COM例外を引き起こす。
2. 送信トレイ(Outbox)のブラックボックス化
単に `Send` を実行した場合、メールはいったん送信トレイに入る。しかし、コード側で「本当に送信されたか」のステータスを追跡できず、エラーハンドリングのしようがない。
3. セッションの競合(スレッドセーフの欠如)
複数プロセスやタイマーイベントから同時にOutlookのオブジェクトを操作すると、RPCサーバーが忙殺され、MAPIのセッションがクラッシュする。

これらを解決するためには、「下書き保存(Save)による安全な永続化」と、「ファイルベースのステータス管理(DB連携の代用)」を組み合わせたアーキテクチャが必要となる。

2. 全体アーキテクチャの設計思想

今回構築するシステムの肝は以下の通りだ。

  • 非同期(擬似)アプローチ:即時送信を行わず、確実にローカルストレージ(または下書きフォルダ)へイミュータブル(変更不可)な状態でメールを生成し、送信フラグを立てる。
  • ステータス管理:送信前・送信中・送信完了・エラーの各状態をCSVまたは簡易DB(今回は堅牢性を考慮しテキストログ+プロパティ管理)で追跡する。
  • セルフヒーリング(自己修復):万が一途中でプロセスが落ちても、次回起動時やタイマー実行時に「送信トレイに残ったゾンビメール」を検知し、安全にリトライする。

3. プロダクションコード:堅牢なメール生成&非同期キューイング

以下のコードは、単にメールを作るだけでなく、エラー時のトレースを可能にしたモジュールだ。

‘ =================================================================و
‘ モジュール名: clsAsyncMailDispatcher
‘ 概要: スレッドセーフを考慮したメール生成と安全なキューイング
‘ =================================================================و
Option Explicit

Private Const LOG_PATH As String = “C:\AutomationLogs\MailSystem_Error.log”

‘ ログ出力ラッパー(実務ではDBや専用ロガーに置き換え可能)
Private Sub WriteLog(ByVal message As String)
On Error Resume Next
Dim fileNum As Integer
fileNum = FreeFile
Open LOG_PATH For Append As #fileNum
Print #fileNum, Format(Now, “yyyy-mm-dd hh:nn:ss”) & ” – ” & message
Close #fileNum
On Error GoTo 0
End Sub

‘ 安全なメール作成と下書き保存(即時送信しない)
Public Function CreateAndQueueMail(ByVal sendTo As String, ByVal subject As String, ByVal body As String, Optional ByVal attachmentPath As String = “”) As Boolean
Dim olApp As Object
Dim olNs As Object
Dim olMail As Object

On Error GoTo ErrorHandler

‘ セッションの安全な取得(late bindingによる環境依存の排除)
Set olApp = CreateObject(“Outlook.Application”)
Set olNs = olApp.GetNamespace(“MAPI”)

‘ MailItemの生成
Set olMail = olApp.CreateItem(0) ‘ 0 = olMailItem

With olMail
.To = sendTo
.Subject = subject
.Body = body

If attachmentPath <> “” Then
If Dir(attachmentPath) <> “” Then
.Attachments.Add attachmentPath
Else
Call WriteLog(“警告: 添付ファイルが見つかりません -> ” & attachmentPath)
End If
End If

‘ 【重要】ここで直に .Send を叩かない!
‘ 下書き(Drafts)または専用の保留フォルダに保存し、IDを確定させる
.Save

‘ カスタムプロパティにメタデータを付与して追跡可能にする
‘ (UserPropertiesを活用することで、メールの識別子を維持)
Dim prop As Object
Set prop = .UserProperties.Add(“SystemQueueStatus”, 1) ‘ 1: 待機中
Set prop = .UserProperties.Add(“SystemRetryCount”, 0)
.Save

Call WriteLog(To: Successfully queued mail to [” & sendTo & “], EntryID: ” & .EntryID)
End With

CreateAndQueueMail = True
Exit Function

ErrorHandler:
Call WriteLog(“CRITICAL ERROR in CreateAndQueueMail: ” & Err.Description & ” (Code: ” & Err.Number & “)”)
CreateAndQueueMail = False
End Function

4. エラーリカバリ&自動再送監視エンジン

ここからが本番だ。送信トレイに残留したメールや、エラーステータスになったアイテムを監視し、リトライ上限(例: 3回)まで自動送信を試みるエンジンの実装である。

‘ =================================================================و
‘ モジュール名: modMailRecoveryEngine
‘ 概要: 送信トレイ監視とエラーリカバリの実行エンジン
‘ =================================================================و
Option Explicit

Public Sub RunRecoveryEngine()
Dim olApp As Object
Dim olNs As Object
Dim olFolder As Object
Dim olMail As Object
Dim i As Long
Dim retryCount As Long
Dim maxRetries As Long

maxRetries = 3

On Error GoTo EngineError

Set olApp = CreateObject(“Outlook.Application”)
Set olNs = olApp.GetNamespace(“MAPI”)

‘ 下書きフォルダ(またはカスタムキューフォルダ)を取得
‘ olFolderDrafts = 16
Set olFolder = olNs.GetDefaultFolder(16)

‘ 逆順ループ(コレクションを操作しながら削除・移動するため)
For i = olFolder.Items.Count To 1 Step -1
If TypeName(olFolder.Items(i)) = “MailItem” Then
Set olMail = olFolder.Items(i)

‘ カスタムプロパティの安全な取得
On Error Resume Next
retryCount = olMail.UserProperties(“SystemRetryCount”).Value
Dim queueStatus As Long
queueStatus = olMail.UserProperties(“SystemQueueStatus”).Value
On Error GoTo EngineError

‘ ステータスが「待機中(1)」または「エラー(2)」のものを処理
If queueStatus = 1 Or queueStatus = 2 Then
If retryCount < maxRetries Then On Error GoTo SendFailed ' ここで初めて実際の送信を試みる olMail.Send ' 送信成功時の処理 Debug.Print "リカバリ成功: " & olMail.Subject On Error GoTo EngineError Else ' 最大リトライ超過:デッドレター(隔離)フォルダへ移動するなどの処理 olMail.UserProperties("SystemQueueStatus").Value = 99 ' 99: 致命的エラー olMail.Save MsgBox "メール送信が最大試行回数を超えて失敗しました: " & olMail.Subject, vbCritical End If End If End If Next i Exit Sub SendFailed: ' 送信個別失敗時のハンドリング retryCount = retryCount + 1 olMail.UserProperties("SystemRetryCount").Value = retryCount olMail.UserProperties("SystemQueueStatus").Value = 2 ' 2: エラー(要リトライ) olMail.Save Debug.Print "送信失敗 (リトライ回数: " & retryCount & "): " & Err.Description Resume Next EngineError: MsgBox "リカバリエンジンで致命的なエラーが発生しました: " & Err.Description, vbCritical End Sub ---

5. ファイル連携・データベース連携における実務上の注意点

本システムをさらに拡張し、外部のExcelファイルやRDB(SQL Server / SQLite等)と連携させる場合、以下のアーキテクチャ上の鉄則を守ってほしい。

1. 排他制御(File Locking)の回避
複数のVBAインスタンスや外部タスクスケジューラからログファイルや管理用CSVに同時アクセスすると、ガチガチのファイルロック競合エラー(Error 70: 書き込み権限がありません)が発生する。

  • 対策: ログ書き込みやステータス更新は必ずトランザクション的(短時間でファイルを閉じる)に行い、可能であれば専用のキューテーブルを持つDBをバックエンドに据えること。

2. EntryIDの永続性
Outlookの `EntryID` は、アイテムがフォルダを移動したり、別のストア(PST/OSTファイル)間で同期されたりすると変化することがある。

  • 対策: メールの追跡には `EntryID` だけでなく、独自のUUIDをカスタムプロパティ(`UserProperties`)に埋め込んでおき、外部システムとのキーとして利用するのがプロの技だ。

6. チーフアーキテクトからの総括

「とりあえず動くコード」を書くことは、プログラミングを覚えたてのジュニアでもできる。しかし、「障害が起きることを前提に、システムが自律的にリカバリするコード」を書けるかどうかが、プロのエンジニアと単なるスクリプトキディを分ける境界線だ。

今回紹介した非同期キューイングとリカバリ監視の概念は、Outlook VBAだけでなく、あらゆる業務自動化システムの根底に通じるデザインパターンである。ぜひ自身の開発環境に導入し、強靭なバックオフィスシステムを構築してほしい。

タイトルとURLをコピーしました