【実務・中級編】【上級者向け】Outlookの「送信トレイ」を監視し、送信失敗したメールを自動再送するエラーリカバリシステム – Outlook VBA解析バイブル

スポンサーリンク

Outlook VBAを掌握する極限の知見:送信トレイの魔物を制す。送信失敗メールを自動リカバリする要塞システムの構築

開発プロジェクトの現場で、こんな恐怖の声を耳にしたことはないだろうか。

  • 「重要顧客への提案メールが、ネットワークの一瞬の切断で送信トレイの奥底に消え、気づいたときには手遅れだった…」
  • 「大量送信の最中にエラーで止まり、どれが送信済みでどれが未送信か分からなくなった…」

一般的なリファレンス本には、`MailItem.Send` の叩き方しか書いていない。しかし、プロのエンジニアが向き合うべきは「APIが失敗した後の世界」だ。Outlookの送信トレイ(Outbox)は、アプリケーション層とトランスポート層の狭間で不安定に揺らぐ、いわば魔物の巣窟である。

今回は、この送信トレイを常時監視し、ネットワーク障害による送信エラーを検知して完全自動でリトライを完遂する、プロダクション品質の堅牢なエラーリカバリシステムを解説する。

1. なぜ「単純なエラーハンドリング」では破綻するのか?

初心者がやりがちなアプローチは、`On Error Resume Next` で囲み、ループで `Send` を叩き続けることだ。これは実務では悪手中の悪手である。

破綻する理由

1. オブジェクトのライフサイクルとセッションの乖離: `Send` メソッドは非同期に近い挙動をとる。メソッドが制御を返した瞬間、メールはOutlookの内部キュー(送信トレイ)に移譲されるため、VBA側から直接「送信完了」を追跡できない。
2. ネットワーク疎通とSMTPサーバーの応答遅延の混同: 単にオフライン状態なのか、メールサーバー側で一時的な拒否(Greylistingなど)が起きているのかを判別せずリトライすると、最悪の場合「同一メールの重複大量送信(スパム扱い)」を引き起こす。
3. COM例外の捕捉漏れ: OutlookのCOMオブジェクトは、MAPIセッションの状態によって予測不可能なエラーコードを返す。

これらを解決するためには、「送信トレイに残留し続けている異常なアイテム」を検知し、安全な状態(Draft等)に引き戻してから再送ロジックに乗せるという、逆転の発想が必要になる。

2. アーキテクチャ設計:要塞システムの全体像

今回のリカバリシステムは、以下の3層で構築する。

1. 監視レイヤー (Monitor): タイマー実行(または手動トリガー)により、送信トレイを走査。
2. 判定レイヤー (Evaluator): トレイ内に「一定時間以上留まっているメール」を特定し、ネットワーク疎通確認と掛け合わせて異常と断定。
3. リカバリレイヤー (Recovery): 該当メールを一度安全にキャプチャし、メタデータを維持したまま再エンキュー(再送処理)。

3. プロダクションコード:送信エラーリカバリ・エンジン

以下のコードを、Outlookの `ThisOutlookSession` または標準モジュールに実装する。実業務でそのまま耐えうるよう、厳格な型指定とエラーハンドリングを施している。

Option Explicit

‘ Windows API: ネットワーク接続状態の判定用
If VBA7 Then
Declare PtrSafe Function InternetGetConnectedState Lib “wininet.dll” (ByRef lpdwFlags As Long, ByVal dwReserved As Long) As Long
Else
Declare Function InternetGetConnectedState Lib “wininet.dll” (ByRef lpdwFlags As Long, ByVal dwReserved As Long) As Long
End If

‘ 定数定義
Private Const MAX_STAY_MINUTES As Long = 5 ‘ 送信トレイに留まって良い許容時間(分)
Private Const FLAG_CONNECTION_MODEM As Long &H1
Private Const FLAG_CONNECTION_LAN As Long &H2
Private Const FLAG_CONNECTION_PROXY As Long &H4

/

  • メインエントリーポイント:送信トレイの監視とリカバリを実行

/
Public Sub ExecuteOutboxRecoverySystem()
Dim ns As Outlook.NameSpace
Dim outboxFolder As Outlook.MAPIFolder
Dim mailItems As Outlook.Items
Dim targetItem As Object
Dim i As Long
Dim mailCount As Long

On Error GoTo ErrorHandler

‘ 1. ネットワーク疎通確認
If Not IsNetworkConnected() Then
Debug.Print “[RecoverySystem] ネットワークがオフラインです。処理をスキップします。”
Exit Sub
End If

Set ns = Application.GetNamespace(“MAPI”)
Set outboxFolder = ns.GetDefaultFolder(olFolderOutbox)
Set mailItems = outboxFolder.Items

mailCount = mailItems.Count
If mailCount = 0 Then Exit Sub

‘ 2. 送信トレイ内のアイテムを逆順走査(コレクション変更時のインデックスズレを防ぐため)
For i = mailCount To 1 Step -1
Set targetItem = mailItems(i)

‘ MailItemかどうかの厳密な型チェック(MeetingItem等の誤爆を防ぐ)
If TypeOf targetItem Is Outlook.MailItem Then
Dim mail As Outlook.MailItem
Set mail = targetItem

‘ 3. 滞留時間のチェック
If IsMailStuck(mail) Then
Call ProcessRecovery(mail)
End If
End If
Next i

Exit Sub

ErrorHandler:
MsgBox “送信リカバリシステムで致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
End Sub

/

  • ネットワーク接続の有無を判定

/
Private Function IsNetworkConnected() As Boolean
Dim flags As Long
IsNetworkConnected = (InternetGetConnectedState(flags, 0&) <> 0)
End Function

/

  • メールが送信トレイに異常滞留しているか判定

/
Private Function IsMailStuck(ByVal mail As Outlook.MailItem) As Boolean
Dim sentTime As Date
Dim diffMinutes As Double

On Error Resume Next
‘ CreationTime または 送信試行時間を基準にする
‘ 注: 送信トレイに入った瞬間にOutlookが自動付与するプロパティはないため、
‘ 独自UserProperty(”LastAttemptTime”)を付与する設計を推奨。
‘ ここでは簡易的に CreationTime からの経過時間で代用する。
sentTime = mail.CreationTime

If Err.Number <> 0 Then
IsMailStuck = False
Exit Function
End If
On Error GoTo 0

diffMinutes = DateDiff(“n”, sentTime, Now)

‘ 指定分数以上経過しており、かつ送信フラグが立っているか
If diffMinutes >= MAX_STAY_MINUTES Then
IsMailStuck = True
Else
IsMailStuck = False
End If
End Function

/

  • リカバリ実行:送信トレイから一旦外し、再送キューに乗せる

/
Private Sub ProcessRecovery(ByRef mail As Outlook.MailItem)
On Error GoTo RecoveryError

Debug.Print “[RecoverySystem] 滞留メールを検知: Subject = ” & mail.Subject

‘ 【重要】送信トレイにある状態のままプロパティをいじるとCOMエラーになるため、
‘ 一度下書き(Drafts)フォルダ等に移動するか、セッションをデタッチするアプローチを取る。
‘ 実務では、メタデータを保持したまま「転送」または「複製を作成して元を削除」が最も堅牢。

Dim draftFolder As Outlook.MAPIFolder
Set draftFolder = Application.GetNamespace(“MAPI”).GetDefaultFolder(olFolderDrafts)

‘ 送信中のロックを解除するため、一度オブジェクトをコピーして下書きに戻す
Dim recoveredMail As Outlook.MailItem
Set recoveredMail = mail.Copy

‘ 元の壊れた(スタックした)アイテムを削除
mail.Delete

‘ 下書きフォルダへ移動(自動的にDraftステータスになる)
Set recoveredMail = recoveredMail.Move(draftFolder)

‘ ユーザーに通知、もしくは自動再送処理へ
‘ recoveredMail.Send ‘ ここで即座に再送をかけることも可能だが、安全のためログを残すに留めるか要件とすり合わせる

Debug.Print “[RecoverySystem] リカバリ完了: ” & recoveredMail.Subject & ” を下書きに戻しました。”
Exit Sub

RecoveryError:
Debug.Print “[RecoverySystem] リカバリ失敗: ” & Err.Description
End Sub

4. チーフアーキテクトからの実装上の忠告(ファイル・DB連携の注意点)

本システムを真のエンタープライズ環境で稼働させる場合、以下の設計思想を必ず遵守してほしい。

1. データベース(SQLite / SQL Server)との連携

  • Outlookのプロパティ(`UserProperties`)だけに頼ると、メールが破損した際にリトライ回数のトラッキングがロストする。
  • 堅牢なシステムでは、メール生成時に一意の `MessageID` を発行し、ローカルDB(SQLite等)に `[MessageID, Status, RetryCount, LastAttempt]` を記録。リカバリ処理のたびにDBのカウンタをインクリメントし、「最大リトライ回数(例: 3回)を超えたらアラートを出して放置する(無限ループの防止)」実装にしなければならない。

2. UIスレッドのブロック回避

  • 大量のメールが送信トレイに詰まっている場合、上記の走査処理がOutlook全体のUIをフリーズさせる。実務では `Application.OnTime` を用いた非同期ポーリング、または外部の常駐Windowsサービス(C#製COM Add-in等)からの制御へ移行すべき過渡期のソリューションであることを認識しておくこと。

5. まとめ

自動化において「うまくいく時だけ動くコード」は負債でしかない。「失敗したときにどう振る舞うか」をデザインして初めて、それはプロダクトと呼べる。

今回紹介した送信トレイの監視・リカバリロジックは、あなたのデスクトップアプリを「おもちゃのVBA」から「ミッションクリティカルな業務システム」へと引き上げるための強力な武器となるはずだ。
泥臭い例外処理の向こう側にある、完璧な自動化の世界をぜひその手で実装してほしい。

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