はじめに
TWSNMPのポーリングにメールの受信確認を追加するために試作した、メールサーバーの動作確認やメールボックスの状況(メール数、サイズ、最新メールの日時など)を簡単にチェックできるCLIツールを紹介します。
Go言語で実装されており、IMAPとPOP3の両方に対応しています。
TWSNMPは、
です。
特徴
-
マルチプロトコル対応: IMAP (
imap,imaps) および POP3 (pop3,pop3s) をサポートしています。 - シンプルな操作: 接続情報をURL形式の引数として渡すだけで実行できます。
-
詳細なステータス取得:
- メールボックス内のメッセージ数
- 合計サイズ
- 最新メッセージの日時
- 最新メッセージのID (UID)
ソースコード
全ソースコード(展開するとみられます)
package main
import (
"fmt"
"log"
"net/mail"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/emersion/go-imap/v2"
"github.com/emersion/go-imap/v2/imapclient"
"github.com/knadh/go-pop3"
)
func main() {
// コマンドライン引数のチェック
if len(os.Args) < 2 {
log.Fatalln("usage mailpolling url")
}
url := os.Args[1]
var err error
// URLのプロトコルに応じて処理を分岐
if strings.HasPrefix(url, "imap") {
err = imapPolling(url)
} else if strings.HasPrefix(url, "pop3") {
err = pop3Polling(url)
}
if err != nil {
log.Fatalln(err)
}
}
// IMAPプロトコルによるポーリング処理
func imapPolling(source string) error {
// URLのパース
u, err := url.Parse(source)
if err != nil {
return fmt.Errorf("invalid url: %w", err)
}
server := u.Host
// ポート番号が指定されていない場合のデフォルト設定
if !strings.Contains(server, ":") {
if u.Scheme == "imaps" {
server += ":993"
} else {
server += ":143"
}
}
var c *imapclient.Client
// 接続の確立 (TLSまたはStartTLS)
if u.Scheme == "imaps" || strings.HasSuffix(server, ":993") {
c, err = imapclient.DialTLS(server, nil)
} else {
c, err = imapclient.DialStartTLS(server, nil)
if err != nil {
// StartTLSが失敗した場合は、非セキュアな接続を試行
c, err = imapclient.DialInsecure(server, nil)
}
}
if err != nil {
return fmt.Errorf("connection failed: %w", err)
}
defer c.Logout()
// 認証情報の取得
username := ""
password := ""
if u.User != nil {
username = u.User.Username()
if p, ok := u.User.Password(); ok {
password = p
}
}
// ログイン
if err := c.Login(username, password).Wait(); err != nil {
return fmt.Errorf("login failed: %w", err)
}
// メールボックスの選択 (デフォルトはINBOX)
mbox := "INBOX"
if len(u.Path) > 1 {
mbox = u.Path[1:]
}
status, err := c.Select(mbox, nil).Wait()
if err != nil {
return fmt.Errorf("select failed: %w", err)
}
var totalSize int64
var lastDate time.Time
var lastID string
// メッセージが存在する場合、詳細情報を取得
if status.NumMessages > 0 {
var seqSet imap.SeqSet
seqSet.AddRange(1, status.NumMessages)
fetchOptions := &imap.FetchOptions{RFC822Size: true, InternalDate: true, UID: true}
messages, err := c.Fetch(seqSet, fetchOptions).Collect()
if err != nil {
log.Printf("fetch error: %v", err)
} else {
// 全メッセージのサイズを合算
for _, msg := range messages {
totalSize += msg.RFC822Size
}
// 最終メッセージの情報を取得
if len(messages) > 0 {
lastMsg := messages[len(messages)-1]
lastDate = lastMsg.InternalDate
lastID = strconv.FormatUint(uint64(lastMsg.UID), 10)
}
}
}
// 結果を出力
log.Printf("count=%d size=%d last=%s id=%s err=%v", status.NumMessages, totalSize, lastDate.Format(time.RFC3339), lastID, nil)
return nil
}
// POP3プロトコルによるポーリング処理
func pop3Polling(source string) error {
// URLのパース
u, err := url.Parse(source)
if err != nil {
return fmt.Errorf("invalid url: %w", err)
}
server := u.Hostname()
port := u.Port()
// ポート番号のデフォルト設定
if port == "" {
if u.Scheme == "pop3s" {
port = "995"
} else {
port = "110"
}
}
nport, err := strconv.Atoi(port)
if err != nil {
return fmt.Errorf("invalid port: %w", err)
}
// 認証情報の取得
username := ""
password := ""
if u.User != nil {
username = u.User.Username()
if p, ok := u.User.Password(); ok {
password = p
}
}
// POP3クライアントの設定と接続
p := pop3.New(pop3.Opt{
Host: server,
Port: nport,
TLSEnabled: u.Scheme == "pop3s" || nport == 995,
})
conn, err := p.NewConn()
if err != nil {
return fmt.Errorf("connection failed: %w", err)
}
defer conn.Quit()
// 認証
if err := conn.Auth(username, password); err != nil {
return fmt.Errorf("auth failed: %w", err)
}
// ステータス情報の取得 (メッセージ数と合計サイズ)
count, size, err := conn.Stat()
if err != nil {
return fmt.Errorf("stat failed: %w", err)
}
var lastDate time.Time
var lastID string
if count > 0 {
// 最終メッセージのUIDを取得
if uidls, err := conn.Uidl(count); err == nil && len(uidls) > 0 {
lastID = uidls[0].UID
}
// 最終メッセージのヘッダーから日付を取得
if msg, err := conn.Top(count, 0); err == nil {
dateStr := msg.Header.Get("Date")
if t, err := mail.ParseDate(dateStr); err == nil {
lastDate = t
}
}
}
// 結果を出力
log.Printf("count=%d size=%d last=%s id=%s err=%v", count, size, lastDate.Format(time.RFC3339), lastID, nil)
return nil
}
使用しているライブラリ
本ツールでは、以下のオープンソースライブラリを活用しています。
- github.com/emersion/go-imap/v2: モダンで強力なIMAPライブラリ(v2を使用)。
- github.com/knadh/go-pop3: シンプルで使いやすいPOP3クライアントライブラリ。
インストールとビルド
mkdir mailpolling
cd mailpolling
# main.goにソースコードを内容を保存してください。
go mod init mailpolling
go mod tidy
go build
使い方
実行ファイルに接続先URLを引数として渡して実行します。
構文
./mailpolling <URL>
URLの形式: protocol://user:password@host:port/path
-
protocol:imap,imaps,pop3,pop3s -
path: IMAPの場合、メールボックス名(デフォルトはINBOX)
実行例
IMAP (SSL/TLS) の場合:
./mailpolling imaps://user:pass@imap.example.com:993/INBOX
POP3 (SSL/TLS) の場合:
./mailpolling pop3s://user:pass@pop.example.com:995
出力形式
実行結果は標準ログ出力として表示されます。エラーが発生した場合はエラーメッセージが出力されます。
2025/01/20 10:00:00 count=123 size=456789 last=2025-01-20T09:55:00Z id=12345 err=<nil>
-
count: メッセージ数 -
size: 合計サイズ(バイト) -
last: 最新メッセージの日時 -
id: 最新メッセージのUID -
err: エラー内容(成功時は<nil>)
コードのポイント
メイン処理 (main.go)
URLのスキーム(プロトコル)を見て、IMAP用またはPOP3用の処理関数を呼び分けています。
func main() {
if len(os.Args) < 2 {
log.Fatalln("usage mailpolling url")
}
url := os.Args[1]
// ... (中略) ...
if strings.HasPrefix(url, "imap") {
err = imapPolling(url)
} else if strings.HasPrefix(url, "pop3") {
err = pop3Polling(url)
}
// ...
}
IMAPポーリング (imapPolling)
emersion/go-imap/v2 を使用して実装しています。
DialTLS や DialStartTLS を使い分け、セキュアな接続を優先します。
Fetch コマンドで全メッセージの RFC822Size, InternalDate, UID を取得し、集計しています。
// メッセージ情報の取得部分
fetchOptions := &imap.FetchOptions{RFC822Size: true, InternalDate: true, UID: true}
messages, err := c.Fetch(seqSet, fetchOptions).Collect()
// ... 集計処理 ...
POP3ポーリング (pop3Polling)
knadh/go-pop3 を使用しています。
Stat コマンドで件数とサイズを取得し、Uidl と Top コマンドを組み合わせて最新メッセージのUIDと日付を取得しています。
// ステータスと最新情報の取得
count, size, err := conn.Stat()
// ...
if uidls, err := conn.Uidl(count); err == nil && len(uidls) > 0 {
lastID = uidls[0].UID
}
if msg, err := conn.Top(count, 0); err == nil {
// ヘッダーからDateをパース
}
まとめ
Go言語の強力な標準ライブラリとサードパーティライブラリを組み合わせることで、比較的少ないコード量で実用的なメールサーバー監視ツールを作成できました。
TWSNMPなどの監視システムと連携させることで、メールサーバーの死活監視だけでなく、メールの滞留や着信確認などにも活用できます。