You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Golang与inotify实现Postfix邮件写入完成触发的问题

解决Postfix接收邮件触发Golang代码的优雅方案

你猜的没错——监控目录时,IN_CLOSE_WRITE事件只会针对目录本身的操作触发,不会感知目录内文件的写入完成。结合Maildir的工作原理,其实根本不需要监控文件的写入事件,用下面的方法就能完美规避竞态和sleep的问题:

核心原理:利用Maildir的原子性移动特性

Postfix严格遵循Maildir规范:

  • 接收邮件时,先在Maildir/tmp目录创建临时文件,完全写入后,通过操作系统的原子移动操作将文件移到Maildir/new目录
  • 原子移动意味着:要么文件完整出现在new目录,要么完全不存在,不存在中间状态

所以只要new目录里出现文件,就代表邮件已经写入完成,直接处理即可。

具体实现步骤

  1. 用fsnotify包监控Maildir/new目录的IN_MOVED_TO和IN_CREATE事件
    • IN_MOVED_TO对应Postfix从tmp移动过来的文件(主要场景)
    • IN_CREATE作为兼容,覆盖某些直接在new创建文件的特殊MTA情况
  2. 处理文件时遵循Maildir规范
    • 先将文件从new目录移动到cur目录(可以加上:2,这类状态标记),避免后续重复处理
    • 再读取cur目录下的文件内容,执行你的业务逻辑

Golang代码示例

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/fsnotify/fsnotify"
)

func main() {
	maildirPath := "/path/to/your/Maildir"
	newDir := filepath.Join(maildirPath, "new")
	curDir := filepath.Join(maildirPath, "cur")

	// 创建监控器
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Printf("创建监控器失败: %v\n", err)
		return
	}
	defer watcher.Close()

	// 监控new目录
	err = watcher.Add(newDir)
	if err != nil {
		fmt.Printf("添加目录监控失败: %v\n", err)
		return
	}

	fmt.Printf("开始监控Maildir/new目录: %s\n", newDir)

	for {
		select {
		case event, ok := <-watcher.Events:
			if !ok {
				return
			}
			// 只处理文件,跳过目录本身的事件
			if (event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Move == fsnotify.Move) {
				if info, err := os.Stat(event.Name); err == nil && !info.IsDir() {
					// 生成cur目录下的目标路径,添加Maildir状态标记
					filename := filepath.Base(event.Name)
					curFilePath := filepath.Join(curDir, fmt.Sprintf("%s:2,", filename))
					// 原子移动文件到cur目录
					err = os.Rename(event.Name, curFilePath)
					if err != nil {
						fmt.Printf("移动文件失败: %v\n", err)
						continue
					}
					// 读取邮件内容并处理
					content, err := os.ReadFile(curFilePath)
					if err != nil {
						fmt.Printf("读取邮件失败: %v\n", err)
						continue
					}
					fmt.Println("收到新邮件,内容长度:", len(content))
					// 在这里执行你的业务逻辑,比如解析邮件、调用接口等
				}
			}
		case err, ok := <-watcher.Errors:
			if !ok {
				return
			}
			fmt.Printf("监控错误: %v\n", err)
		}
	}
}

关键细节说明

  • 为什么不需要监控tmp目录?因为Postfix在tmp里的文件可能还在写入,而且可能会被删除(比如投递失败),直接监控new更可靠
  • 原子移动操作由操作系统保证,不会出现文件还在写入就被处理的情况,彻底规避竞态条件
  • 移动到cur目录是Maildir规范要求,确保即使程序崩溃重启,也不会重复处理同一封邮件

内容的提问来源于stack exchange,提问作者Vitaliy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:05:13