package main import ( "context" "log/slog" "os" "os/signal" "sync" "syscall" "thuanle.me/claw-email-bridge/internal/ai_client" "thuanle.me/claw-email-bridge/internal/api" "thuanle.me/claw-email-bridge/internal/config" "thuanle.me/claw-email-bridge/internal/database" "thuanle.me/claw-email-bridge/internal/logging" "thuanle.me/claw-email-bridge/internal/mail" ) func main() { logging.Setup() cfg, err := config.Load() if err != nil { slog.Error("failed to load config", "error", err) os.Exit(1) } slog.Info("config loaded successfully") db, err := database.Open("data/database.db") if err != nil { slog.Error("failed to open database", "error", err) os.Exit(1) } slog.Info("database ready", "path", "data/database.db") ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Graceful shutdown on SIGINT/SIGTERM. sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-sigCh slog.Info("received signal, shutting down", "signal", sig) cancel() }() var wg sync.WaitGroup // Start IMAP watcher with OpenClaw dispatch. dispatcher := ai_client.NewDispatcher(cfg, db) watcher := mail.NewIMAPWatcher(cfg, db) watcher.OnReceived = func(task *database.Task) { go dispatcher.Dispatch(task) } wg.Add(1) go func() { defer wg.Done() watcher.Run(ctx) }() // Start HTTP server with SMTP egress on callback. smtpSender := mail.NewSMTPSender(cfg, db) router := api.NewRouter(db, cfg.BridgeCallbackToken, smtpSender.SendReplyFunc()) wg.Add(1) go func() { defer wg.Done() slog.Info("starting HTTP server", "addr", cfg.ListenAddr) if err := router.Run(cfg.ListenAddr); err != nil { slog.Error("server exited with error", "error", err) cancel() } }() wg.Wait() slog.Info("bridge stopped") }