Initial commit: email-openclaw bridge v1
Full pipeline: IMAP ingress -> OpenClaw dispatch -> callback -> SMTP reply. SQLite stateful storage with idempotency, threading, and retry logic. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// Config holds all application configuration loaded from environment variables.
|
||||
type Config struct {
|
||||
// IMAP settings
|
||||
IMAPHost string
|
||||
IMAPPort string
|
||||
IMAPUser string
|
||||
IMAPPass string
|
||||
|
||||
// SMTP settings
|
||||
SMTPHost string
|
||||
SMTPPort string
|
||||
SMTPUser string
|
||||
SMTPPass string
|
||||
|
||||
// OpenClaw settings
|
||||
OpenClawURL string
|
||||
OpenClawAPIKey string
|
||||
|
||||
// Bridge settings
|
||||
BridgeCallbackToken string
|
||||
SystemEmail string
|
||||
WhitelistEmails []string
|
||||
|
||||
// Optional
|
||||
IMAPProxyURL string
|
||||
|
||||
// Server
|
||||
ListenAddr string
|
||||
}
|
||||
|
||||
// Load reads .env (if present) and populates Config from environment variables.
|
||||
// Returns an error if any required variable is missing.
|
||||
func Load() (*Config, error) {
|
||||
// Best-effort load .env — ignore error if file doesn't exist.
|
||||
_ = godotenv.Load()
|
||||
|
||||
cfg := &Config{
|
||||
IMAPHost: os.Getenv("IMAP_HOST"),
|
||||
IMAPPort: os.Getenv("IMAP_PORT"),
|
||||
IMAPUser: os.Getenv("IMAP_USER"),
|
||||
IMAPPass: os.Getenv("IMAP_PASS"),
|
||||
SMTPHost: os.Getenv("SMTP_HOST"),
|
||||
SMTPPort: os.Getenv("SMTP_PORT"),
|
||||
SMTPUser: os.Getenv("SMTP_USER"),
|
||||
SMTPPass: os.Getenv("SMTP_PASS"),
|
||||
OpenClawURL: os.Getenv("OPENCLAW_URL"),
|
||||
OpenClawAPIKey: os.Getenv("OPENCLAW_API_KEY"),
|
||||
BridgeCallbackToken: os.Getenv("BRIDGE_CALLBACK_TOKEN"),
|
||||
SystemEmail: os.Getenv("SYSTEM_EMAIL"),
|
||||
IMAPProxyURL: os.Getenv("IMAP_PROXY_URL"),
|
||||
ListenAddr: os.Getenv("LISTEN_ADDR"),
|
||||
}
|
||||
|
||||
if raw := os.Getenv("WHITELIST_EMAILS"); raw != "" {
|
||||
for _, email := range strings.Split(raw, ",") {
|
||||
if trimmed := strings.TrimSpace(email); trimmed != "" {
|
||||
cfg.WhitelistEmails = append(cfg.WhitelistEmails, trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ListenAddr == "" {
|
||||
cfg.ListenAddr = ":8080"
|
||||
}
|
||||
|
||||
if err := cfg.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// validate checks that all required configuration values are present.
|
||||
func (c *Config) validate() error {
|
||||
required := map[string]string{
|
||||
"IMAP_HOST": c.IMAPHost,
|
||||
"IMAP_PORT": c.IMAPPort,
|
||||
"IMAP_USER": c.IMAPUser,
|
||||
"IMAP_PASS": c.IMAPPass,
|
||||
"SMTP_HOST": c.SMTPHost,
|
||||
"SMTP_PORT": c.SMTPPort,
|
||||
"SMTP_USER": c.SMTPUser,
|
||||
"SMTP_PASS": c.SMTPPass,
|
||||
"OPENCLAW_URL": c.OpenClawURL,
|
||||
"BRIDGE_CALLBACK_TOKEN": c.BridgeCallbackToken,
|
||||
"SYSTEM_EMAIL": c.SystemEmail,
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for name, val := range required {
|
||||
if val == "" {
|
||||
missing = append(missing, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("missing required config: %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user