feat(email): pkg/email + Mailpit docker-compose service (ADR-0029 Phase A.1) (#59)
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 13s
CI/CD Pipeline / CI Pipeline (push) Successful in 4m3s
CI/CD Pipeline / Trigger Docker Push (push) Successful in 4s

Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #59.
This commit is contained in:
2026-05-05 10:47:03 +02:00
committed by arcodange
parent 235cc41f68
commit ef32e750ed
6 changed files with 482 additions and 3 deletions

View File

@@ -104,9 +104,22 @@ type APIConfig struct {
// AuthConfig holds authentication configuration
type AuthConfig struct {
JWTSecret string `mapstructure:"jwt_secret"`
AdminMasterPassword string `mapstructure:"admin_master_password"`
JWT JWTConfig `mapstructure:"jwt"`
JWTSecret string `mapstructure:"jwt_secret"`
AdminMasterPassword string `mapstructure:"admin_master_password"`
JWT JWTConfig `mapstructure:"jwt"`
Email EmailConfig `mapstructure:"email"`
}
// EmailConfig holds outgoing email transport configuration.
// Defaults match local Mailpit (cf. ADR-0029) so dev needs no extra setup.
type EmailConfig struct {
From string `mapstructure:"from"`
SMTPHost string `mapstructure:"smtp_host"`
SMTPPort int `mapstructure:"smtp_port"`
SMTPUsername string `mapstructure:"smtp_username"`
SMTPPassword string `mapstructure:"smtp_password"`
SMTPUseTLS bool `mapstructure:"smtp_use_tls"`
Timeout time.Duration `mapstructure:"timeout"`
}
// JWTConfig holds JWT-specific configuration
@@ -256,6 +269,13 @@ func LoadConfig() (*Config, error) {
v.SetDefault("auth.jwt.secret_retention.max_retention", 72*time.Hour)
v.SetDefault("auth.jwt.secret_retention.cleanup_interval", 1*time.Hour)
// Email defaults — match local Mailpit (ADR-0029).
v.SetDefault("auth.email.from", "noreply@dance-lessons-coach.local")
v.SetDefault("auth.email.smtp_host", "localhost")
v.SetDefault("auth.email.smtp_port", 1025)
v.SetDefault("auth.email.smtp_use_tls", false)
v.SetDefault("auth.email.timeout", 10*time.Second)
// Check for custom config file path via environment variable
if configFile := os.Getenv("DLC_CONFIG_FILE"); configFile != "" {
v.SetConfigFile(configFile)
@@ -301,6 +321,13 @@ func LoadConfig() (*Config, error) {
v.BindEnv("auth.jwt.secret_retention.retention_factor", "DLC_AUTH_JWT_SECRET_RETENTION_FACTOR")
v.BindEnv("auth.jwt.secret_retention.max_retention", "DLC_AUTH_JWT_SECRET_MAX_RETENTION")
v.BindEnv("auth.jwt.secret_retention.cleanup_interval", "DLC_AUTH_JWT_SECRET_CLEANUP_INTERVAL")
v.BindEnv("auth.email.from", "DLC_AUTH_EMAIL_FROM")
v.BindEnv("auth.email.smtp_host", "DLC_AUTH_EMAIL_SMTP_HOST")
v.BindEnv("auth.email.smtp_port", "DLC_AUTH_EMAIL_SMTP_PORT")
v.BindEnv("auth.email.smtp_username", "DLC_AUTH_EMAIL_SMTP_USERNAME")
v.BindEnv("auth.email.smtp_password", "DLC_AUTH_EMAIL_SMTP_PASSWORD")
v.BindEnv("auth.email.smtp_use_tls", "DLC_AUTH_EMAIL_SMTP_USE_TLS")
v.BindEnv("auth.email.timeout", "DLC_AUTH_EMAIL_TIMEOUT")
v.BindEnv("telemetry.sampler.type", "DLC_TELEMETRY_SAMPLER_TYPE")
v.BindEnv("telemetry.sampler.ratio", "DLC_TELEMETRY_SAMPLER_RATIO")
@@ -432,6 +459,13 @@ func (c *Config) GetAdminMasterPassword() string {
return c.Auth.AdminMasterPassword
}
// GetEmailConfig returns the outgoing email transport configuration.
// Defaults match local Mailpit (localhost:1025, no TLS, no auth) per
// ADR-0029. Used by pkg/email.NewSMTPSender.
func (c *Config) GetEmailConfig() EmailConfig {
return c.Auth.Email
}
// GetJWTTTL returns the JWT TTL
func (c *Config) GetJWTTTL() time.Duration {
if c.Auth.JWT.TTL == 0 {