Repository: prometheus/alertmanager
File: config/notifiers.go (Go)

Code before:
}

// EmailConfig configures notifications via mail.
type EmailConfig struct {
	NotifierConfig `yaml:",inline" json:",inline"`

	// Email address to notify.
	To               string               `yaml:"to,omitempty" json:"to,omitempty"`
	From             string               `yaml:"from,omitempty" json:"from,omitempty"`
	Hello            string               `yaml:"hello,omitempty" json:"hello,omitempty"`
	Smarthost        HostPort             `yaml:"smarthost,omitempty" json:"smarthost,omitempty"`
	AuthUsername     string               `yaml:"auth_username,omitempty" json:"auth_username,omitempty"`
	AuthPassword     Secret               `yaml:"auth_password,omitempty" json:"auth_password,omitempty"`
	AuthPasswordFile string               `yaml:"auth_password_file,omitempty" json:"auth_password_file,omitempty"`
	AuthSecret       Secret               `yaml:"auth_secret,omitempty" json:"auth_secret,omitempty"`
	AuthSecretFile   string               `yaml:"auth_secret_file,omitempty" json:"auth_secret_file,omitempty"`
	AuthIdentity     string               `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"`
	Headers          map[string]string    `yaml:"headers,omitempty" json:"headers,omitempty"`
	HTML             string               `yaml:"html,omitempty" json:"html,omitempty"`
	Text             string               `yaml:"text,omitempty" json:"text,omitempty"`
	RequireTLS       *bool                `yaml:"require_tls,omitempty" json:"require_tls,omitempty"`
	TLSConfig        *commoncfg.TLSConfig `yaml:"tls_config,omitempty" json:"tls_config,omitempty"`
	// ImplicitTLS controls whether to use implicit TLS (direct TLS connection).
	// true: force use of implicit TLS (direct TLS connection)
	// nil (default): auto-detect based on port (465=implicit, other=explicit) for backward compatibility
	ImplicitTLS      *bool                `yaml:"implicit_tls,omitempty" json:"implicit_tls,omitempty"`
	Threading        ThreadingConfig      `yaml:"threading,omitempty" json:"threading,omitempty"`
}

// ThreadingConfig configures mail threading.
type ThreadingConfig struct {
	Enabled      bool   `yaml:"enabled,omitempty" json:"enabled,omitempty"`
	ThreadByDate string `yaml:"thread_by_date,omitempty" json:"thread_by_date,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *EmailConfig) UnmarshalYAML(unmarshal func(any) error) error {
	*c = DefaultEmailConfig
	type plain EmailConfig
	if err := unmarshal((*plain)(c)); err != nil {
		return err
	}
	if c.To == "" {
		return errors.New("missing to address in email config")
	}
	// Header names are case-insensitive, check for collisions.
	normalizedHeaders := map[string]string{}
	for h, v := range c.Headers {
		normalized := textproto.CanonicalMIMEHeaderKey(h)
		if _, ok := normalizedHeaders[normalized]; ok {
			return fmt.Errorf("duplicate header %q in email config", normalized)

Review comment (suggestion):
Two small bikesheds: I think this should be called `ForceImplicitTLS` since implicit TLS is _always_ enabled for port 465. 

I also think this should be available as a global setting (like `smtp_force_implicit_tls`) since I think it's common to configure one smtp configuration for all email receivers. 

https://github.com/prometheus/alertmanager/blob/3a2c66e7acff848f5ae480120e64fcee29d9d366/config/config.go#L984-L994 

Code after:
}

// EmailConfig configures notifications via mail.
type EmailConfig struct {
	NotifierConfig `yaml:",inline" json:",inline"`

	// Email address to notify.
	To               string               `yaml:"to,omitempty" json:"to,omitempty"`
	From             string               `yaml:"from,omitempty" json:"from,omitempty"`
	Hello            string               `yaml:"hello,omitempty" json:"hello,omitempty"`
	Smarthost        HostPort             `yaml:"smarthost,omitempty" json:"smarthost,omitempty"`
	AuthUsername     string               `yaml:"auth_username,omitempty" json:"auth_username,omitempty"`
	AuthPassword     Secret               `yaml:"auth_password,omitempty" json:"auth_password,omitempty"`
	AuthPasswordFile string               `yaml:"auth_password_file,omitempty" json:"auth_password_file,omitempty"`
	AuthSecret       Secret               `yaml:"auth_secret,omitempty" json:"auth_secret,omitempty"`
	AuthSecretFile   string               `yaml:"auth_secret_file,omitempty" json:"auth_secret_file,omitempty"`
	AuthIdentity     string               `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"`
	Headers          map[string]string    `yaml:"headers,omitempty" json:"headers,omitempty"`
	HTML             string               `yaml:"html,omitempty" json:"html,omitempty"`
	Text             string               `yaml:"text,omitempty" json:"text,omitempty"`
	RequireTLS       *bool                `yaml:"require_tls,omitempty" json:"require_tls,omitempty"`
	TLSConfig        *commoncfg.TLSConfig `yaml:"tls_config,omitempty" json:"tls_config,omitempty"`
	// ForceImplicitTLS controls whether to use implicit TLS (direct TLS connection).
	// true: force use of implicit TLS (direct TLS connection)
	// false: force disable implicit TLS (use explicit TLS/STARTTLS if required)
	// nil (default): auto-detect based on port (465=implicit, other=explicit) for backward compatibility
	ForceImplicitTLS *bool           `yaml:"force_implicit_tls,omitempty" json:"force_implicit_tls,omitempty"`
	Threading        ThreadingConfig `yaml:"threading,omitempty" json:"threading,omitempty"`
}

// ThreadingConfig configures mail threading.
type ThreadingConfig struct {
	Enabled      bool   `yaml:"enabled,omitempty" json:"enabled,omitempty"`
	ThreadByDate string `yaml:"thread_by_date,omitempty" json:"thread_by_date,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *EmailConfig) UnmarshalYAML(unmarshal func(any) error) error {
	*c = DefaultEmailConfig
	type plain EmailConfig
	if err := unmarshal((*plain)(c)); err != nil {
		return err
	}
	if c.To == "" {
		return errors.New("missing to address in email config")
	}
	// Header names are case-insensitive, check for collisions.
	normalizedHeaders := map[string]string{}
	for h, v := range c.Headers {
		normalized := textproto.CanonicalMIMEHeaderKey(h)
		if _, ok := normalizedHeaders[normalized]; ok {