Repository: authelia/authelia (go)

Issue:
Allow to set FROM email header in smtp notifier
## Feature Request

allow setting the `FROM` smtp header in smtp notifier

### Description

Currently only the `sender` configuration key can be set, which sets the `MAIL FROM` SMTP header as well as the `From` header in the email body.

The documentation states possible limitations on the `sender` key, namely 

> It should be noted that some SMTP servers require the username provided to have access to send from the specific address listed here.

These restrictions are only relevant for the `MAIL FROM` SMTP header.

On the other hand, setting the `From` header individually would allow the emails send by authelia to show up much nicer in email programs since it would be possible to use the `Authelia <admin@example.com>` format. `Authelia` can be set to any string desired and shows up as the email's sender in mail programs.

For difference between `MAIL FROM` and `From`:
https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_transport_example

### Use Case

Improve email formatting of smtp notifier. Especially important when you don't own a custom mail server where you can set up a custom email for every service. 

Pull request description:
fix(notifier): force use of sender email in smtp from cmd
This change addresses an issue with the usage of the full sender configuration option in the `MAIL FROM` SMTP command. If a user includes a name in the sender this shouldn't be sent in the `MAIL FROM` command, instead we should extract it and use just the email portion.

Fixes #2571

Patch:
diff --git a/config.template.yml b/config.template.yml
index 578701a8a..732dfdb52 100644
--- a/config.template.yml
+++ b/config.template.yml
@@ -591,8 +591,10 @@ notifier:
     ## Can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
     password: password
 
-    ## The address to send the email FROM.
-    sender: admin@example.com
+    ## The sender is used to is used for the MAIL FROM command and the FROM header.
+    ## If this is not defined and the username is an email, we use the username as this value. This can either be just
+    ## an email address or the RFC5322 'Name <email address>' format.
+    sender: "Authelia <admin@example.com>"
 
     ## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
     identifier: localhost
diff --git a/docs/configuration/notifier/smtp.md b/docs/configuration/notifier/smtp.md
index 7f506408e..706904586 100644
--- a/docs/configuration/notifier/smtp.md
+++ b/docs/configuration/notifier/smtp.md
@@ -21,7 +21,7 @@ notifier:
     timeout: 5s
     username: test
     password: password
-    sender: admin@example.com
+    sender: "Authelia <admin@example.com>"
     identifier: localhost
     subject: "[Authelia] {title}"
     startup_check_address: test@authelia.com
@@ -103,8 +103,13 @@ required: yes
 {: .label .label-config .label-red }
 </div>
 
-The address sent in the FROM header for the email. Basically who the email appears to come from. It should be noted
-that some SMTP servers require the username provided to have access to send from the specific address listed here.
+The sender is used to construct both the SMTP command `MAIL FROM` and to add the `FROM` header. This address must be
+in [RFC5322](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4) format. This means it must one of two formats:
+- jsmith@domain.com
+- John Smith <jsmith@domain.com>
+
+The `MAIL FROM` command sent to SMTP servers will not include the name portion, this is only set in the `FROM` as per
+specifications.
 
 ### identifier
 <div markdown="1">
diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml
index 578701a8a..732dfdb52 100644
--- a/internal/configuration/config.template.yml
+++ b/internal/configuration/config.template.yml
@@ -591,8 +591,10 @@ notifier:
     ## Can also be set using a secret: https://www.authelia.com/docs/configuration/secrets.html
     password: password
 
-    ## The address to send the email FROM.
-    sender: admin@example.com
+    ## The sender is used to is used for the MAIL FROM command and the FROM header.
+    ## If this is not defined and the username is an email, we use the username as this value. This can either be just
+    ## an email address or the RFC5322 'Name <email address>' format.
+    sender: "Authelia <admin@example.com>"
 
     ## HELO/EHLO Identifier. Some SMTP Servers may reject the default of localhost.
     identifier: localhost
diff --git a/internal/configuration/decode_hooks.go b/internal/configuration/decode_hooks.go
new file mode 100644
index 000000000..b2012d9ff
--- /dev/null
+++ b/internal/configuration/decode_hooks.go
@@ -0,0 +1,35 @@
+package configuration
+
+import (
+	"fmt"
+	"net/mail"
+	"reflect"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+// StringToMailAddressFunc decodes a string into a mail.Address.
+func StringToMailAddressFunc() mapstructure.DecodeHookFunc {
+	return func(f reflect.Kind, t reflect.Kind, data interface{}) (value interface{}, err error) {
+		if f != reflect.String || t != reflect.TypeOf(mail.Address{}).Kind() {
+			return data, nil
+		}
+
+		dataStr := data.(string)
+
+		if dataStr == "" {
+			return mail.Address{}, nil
+		}
+
+		var (
+			mailAddress *mail.Address
+		)
+
+		mailAddress, err = mail.ParseAddress(dataStr)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse '%s' as a RFC5322 address: %w", dataStr, err)
+		}
+
+		return *mailAddress, nil
+	}
+}
diff --git a/internal/configuration/provider.go b/internal/configuration/provider.go
index 5317991cc..ba7243ee0 100644
--- a/internal/configuration/provider.go
+++ b/internal/configuration/provider.go
@@ -45,6 +45,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte
 			DecodeHook: mapstructure.ComposeDecodeHookFunc(
 				mapstructure.StringToTimeDurationHookFunc(),
 				mapstructure.StringToSliceHookFunc(","),
+				StringToMailAddressFunc(),
 			),
 			Metadata:         nil,
 			Result:           o,
diff --git a/internal/configuration/schema/notifier.go b/internal/configuration/schema/notifier.go
index 15aae34c3..29e62aea9 100644
--- a/internal/configuration/schema/notifier.go
+++ b/internal/configuration/schema/notifier.go
@@ -1,6 +1,9 @@
 package schema
 
-import "time"
+import (
+	"net/mail"
+	"time"
+)
 
 // FileSystemNotifierConfiguration represents the configuration of the notifier writing emails in a file.
 type FileSystemNotifierConfiguration struct {
@@ -15,7 +18,7 @@ type SMTPNotifierConfiguration struct {
 	Username            string        `koanf:"username"`
 	Password            string        `koanf:"password"`
 	Identifier          string        `koanf:"identifier"`
-	Sender              string        `koanf:"sender"`
+	Sender              mail.Address  `koanf:"sender"`
 	Subject             string        `koanf:"subject"`
 	StartupCheckAddress string        `koanf:"startup_check_address"`
 	DisableRequireTLS   bool          `koanf:"disable_require_tls"`
@@ -32,9 +35,10 @@ type NotifierConfiguration struct {
 
 // DefaultSMTPNotifierConfiguration represents default configuration parameters for the SMTP notifier.
 var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
-	Timeout:    time.Second * 5,
-	Subject:    "[Authelia] {title}",
-	Identifier: "localhost",
+	Timeout:             time.Second * 5,
+	Subject:             "[Authelia] {title}",
+	Identifier:          "localhost",
+	StartupCheckAddress: "test@authelia.com",
 	TLS: &TLSConfig{
 		MinimumVersion: "TLS1.2",
 	},
diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go
index 261743e93..024defc28 100644
--- a/internal/configuration/validator/notifier.go
+++ b/internal/configuration/validator/notifier.go
@@ -31,7 +31,7 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc
 
 func validateSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
 	if configuration.StartupCheckAddress == "" {
-		configuration.StartupCheckAddress = "test@authelia.com"
+		configuration.StartupCheckAddress = schema.DefaultSMTPNotifierConfiguration.StartupCheckAddress
 	}
 
 	if configuration.Host == "" {
@@ -46,7 +46,7 @@ func validateSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, valid
 		configuration.Timeout = schema.DefaultSMTPNotifierConfiguration.Timeout
 	}
 
-	if configuration.Sender == "" {
+	if configuration.Sender.Address == "" {
 		validator.Push(fmt.Errorf(errFmtNotifierSMTPNotConfigured, "sender"))
 	}
 
diff --git a/internal/notification/const.go b/internal/notification/const.go
index b861fe8c8..312ec5f88 100644
--- a/internal/notification/const.go
+++ b/internal/notification/const.go
@@ -1,4 +1,9 @@
 package notification
 
-const fileNotifierMode = 0600
-const rfc5322DateTimeLayout = "Mon, 2 Jan 2006 15:04:05 -0700"
+const (
+	fileNotifierMode = 0600
+)
+
+const (
+	rfc5322DateTimeLayout = "Mon, 2 Jan 2006 15:04:05 -0700"
+)
diff --git a/internal/notification/smtp_notifier.go b/internal/notification/smtp_notifier.go
index 332712704..feefe0c35 100644
--- a/internal/notification/smtp_notifier.go
+++ b/internal/notification/smtp_notifier.go
@@ -10,6 +10,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/sirupsen/logrus"
+
 	"github.com/authelia/authelia/v4/internal/configuration/schema"
 	"github.com/authelia/authelia/v4/internal/logging"
 	"github.com/authelia/authelia/v4/internal/utils"
@@ -20,6 +22,7 @@ type SMTPNotifier struct {
 	configuration *schema.SMTPNotifierConfiguration
 	client        *smtp.Client
 	tlsConfig     *tls.Config
+	log           *logrus.Logger
 }
 
 // NewSMTPNotifier creates a SMTPNotifier using the notifier configuration.
@@ -27,6 +30,7 @@ func NewSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, certPool *
 	notifier := &SMTPNotifier{
 		configuration: configuration,
 		tlsConfig:     utils.NewTLSConfig(configuration.TLS, tls.VersionTLS12, certPool),
+		log:           logging.Logger(),
 	}
 
 	return notifier
@@ -34,26 +38,25 @@ func NewSMTPNotifier(configuration *schema.SMTPNotifierConfiguration, certPool *
 
 // Do startTLS if available (some servers only provide the auth extension after, and encryption is preferred).
 func (n *SMTPNotifier) startTLS() error {
-	logger := logging.Logger()
 	// Only start if not already encrypted
 	if _, ok := n.client.TLSConnectionState(); ok {
-		logger.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
+		n.log.Debugf("Notifier SMTP connection is already encrypted, skipping STARTTLS")
 		return nil
 	}
 
 	switch ok, _ := n.client.Extension("STARTTLS"); ok {
 	case true:
-		logger.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName)
+		n.log.Debugf("Notifier SMTP server supports STARTTLS (disableVerifyCert: %t, ServerName: %s), attempting", n.tlsConfig.InsecureSkipVerify, n.tlsConfig.ServerName)
 
 		if err := n.client.StartTLS(n.tlsConfig); err != nil {
 			return err
 		}
 
-		logger.Debug("Notifier SMTP STARTTLS completed without error")
+		n.log.Debug("Notifier SMTP STARTTLS completed without error")
 	default:
 		switch n.configuration.DisableRequireTLS {
 		case true:
-			logger.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
+			n.log.Warn("Notifier SMTP server does not support STARTTLS and SMTP configuration is set to disable the TLS requirement (only useful for unauthenticated emails over plain text)")
 		default:
 			return errors.New("Notifier SMTP server does not support TLS and it is required by default (see documentation if you want to disable this highly recommended requirement)")
 		}
@@ -64,7 +67,6 @@ func (n *SMTPNotifier) startTLS() error {
 
 // Attempt Authentication.
 func (n *SMTPNotifier) auth() error {
-	logger := logging.Logger()
 	// Attempt AUTH if password is specified only.
 	if n.configuration.Password != "" {
 		_, ok := n.client.TLSConnectionState()
@@ -77,18 +79,18 @@ func (n *SMTPNotifier) auth() error {
 		if ok {
 			var auth smtp.Auth
 
-			logger.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
+			n.log.Debugf("Notifier SMTP server supports authentication with the following mechanisms: %s", m)
 			mechanisms := strings.Split(m, " ")
 
 			// Adaptively select the AUTH mechanism to use based on what the server advertised.
 			if utils.IsStringInSlice("PLAIN", mechanisms) {
 				auth = smtp.PlainAuth("", n.configuration.Username, n.configuration.Password, n.configuration.Host)
 
-				logger.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
+				n.log.Debug("Notifier SMTP client attempting AUTH PLAIN with server")
 			} else if utils.IsStringInSlice("LOGIN", mechanisms) {
 				auth = newLoginAuth(n.configuration.Username, n.configuration.Password, n.configuration.Host)
 
-				logger.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
+				n.log.Debug("Notifier SMTP client attempting AUTH LOGIN with server")
 			}
 
 			// Throw error since AUTH extension is not supported.
@@ -101,7 +103,7 @@ func (n *SMTPNotifier) auth() error {
 				return err
 			}
 
-			logger.Debug("Notifier SMTP client authenticated successfully with the server")
+			n.log.Debug("Notifier SMTP client authenticated successfully with the server")
 
 			return nil
 		}
@@ -109,14 +111,13 @@ func (n *SMTPNotifier) auth() error {
 		return errors.New("Notifier SMTP server does not advertise the AUTH extension but config requires AUTH (password specified), either disable AUTH, or use an SMTP host that supports AUTH PLAIN or AUTH LOGIN")
 	}
 
-	logger.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
+	n.log.Debug("Notifier SMTP config has no password specified so authentication is being skipped")
 
 	return nil
 }
 
 func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error {
-	logger := logging.Logger()
-	logger.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)
+	n.log.Debugf("Notifier SMTP client attempting to send email body to %s", recipient)
 
 	if !n.configuration.DisableRequireTLS {
 		_, ok := n.client.TLSConnectionState()
@@ -127,7 +128,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
 
 	wc, err := n.client.Data()
 	if err != nil {
-		logger.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err)
+		n.log.Debugf("Notifier SMTP client error while obtaining WriteCloser: %s", err)
 		return err
 	}
 
@@ -136,7 +137,7 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
 	now := time.Now()
 
 	msg := "Date:" + now.Format(rfc5322DateTimeLayout) + "\n" +
-		"From: " + n.configuration.Sender + "\n" +
+		"From: " + n.configuration.Sender.String() + "\n" +
 		"To: " + recipient + "\n" +
 		"Subject: " + subject + "\n" +
 		"MIME-version: 1.0\n" +
@@ -157,13 +158,13 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
 
 	_, err = fmt.Fprint(wc, msg)
 	if err != nil {
-		logger.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
+		n.log.Debugf("Notifier SMTP client error while sending email body over WriteCloser: %s", err)
 		return err
 	}
 
 	err = wc.Close()
 	if err != nil {
-		logger.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err)
+		n.log.Debugf("Notifier SMTP client error while closing the WriteCloser: %s", err)
 		return err
 	}
 
@@ -172,20 +173,16 @@ func (n *SMTPNotifier) compose(recipient, subject, body, htmlBody string) error
 
 // Dial the SMTP server with the SMTPNotifier config.
 func (n *SMTPNotifier) dial() (err error) {
-	logger := logging.Logger()
-	logger.Debugf("Notifier SMTP client attempting connection to %s:%d", n.configuration.Host, n.configuration.Port)
-
 	var (
 		client *smtp.Client
 		conn   net.Conn
+		dialer = &net.Dialer{Timeout: n.configuration.Timeout}
 	)
 
-	dialer := &net.Dialer{
-		Timeout: n.configuration.Timeout,
-	}
+	n.log.Debugf("Notifier SMTP client attempting connection to %s:%d", n.configuration.Host, n.configuration.Port)
 
 	if n.configuration.Port == 465 {
-		logger.Infof("Notifier SMTP client using submissions port 465. Make sure the mail server you are connecting to is configured for submissions and not SMTPS.")
+		n.log.Infof("Notifier SMTP client using submissions port 465. Make sure the mail server you are connecting to is configured for submissions and not SMTPS.")
 
 		conn, err = tls.DialWithDialer(dialer, "tcp", fmt.Sprintf("%s:%d", n.configuration.Host, n.configuration.Port), n.tlsConfig)
 		if err != nil {
@@ -205,18 +202,16 @@ func (n *SMTPNotifier) dial() (err error) {
 
 	n.client = client
 
-	logger.Debug("Notifier SMTP client connected successfully")
+	n.log.Debug("Notifier SMTP client connected successfully")
 
 	return nil
 }
 
 // Closes the connection properly.
 func (n *SMTPNotifier) cleanup() {
-	logger := logging.Logger()
-
 	err := n.client.Quit()
 	if err != nil {
-		logger.Warnf("Notifier SMTP client encountered error during cleanup: %s", err)
+		n.log.Warnf("Notifier SMTP client encountered error during cleanup: %s", err)
 	}
 }
 
@@ -240,7 +235,7 @@ func (n *SMTPNotifier) StartupCheck() (err error) {
 		return err
 	}
 
-	if err := n.client.Mail(n.configuration.Sender); err != nil {
+	if err := n.client.Mail(n.configuration.Sender.Address); err != nil {
 		return err
 	}
 
@@ -257,7 +252,6 @@ func (n *SMTPNotifier) StartupCheck() (err error) {
 
 // Send is used to send an email to a recipient.
 func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
-	logger := logging.Logger()
 	subject := strings.ReplaceAll(n.configuration.Subject, "{title}", title)
 
 	if err := n.dial(); err != nil {
@@ -281,13 +275,13 @@ func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
 	}
 
 	// Set the sender and recipient first.
-	if err := n.client.Mail(n.configuration.Sender); err != nil {
-		logger.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
+	if err := n.client.Mail(n.configuration.Sender.Address); err != nil {
+		n.log.Debugf("Notifier SMTP failed while sending MAIL FROM (using sender) with error: %s", err)
 		return err
 	}
 
 	if err := n.client.Rcpt(recipient); err != nil {
-		logger.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err)
+		n.log.Debugf("Notifier SMTP failed while sending RCPT TO (using recipient) with error: %s", err)
 		return err
 	}
 
@@ -296,7 +290,7 @@ func (n *SMTPNotifier) Send(recipient, title, body, htmlBody string) error {
 		return err
 	}
 
-	logger.Debug("Notifier SMTP client successfully sent email")
+	n.log.Debug("Notifier SMTP client successfully sent email")
 
 	return nil
 }


Test patch:
diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go
index 5d800e7ea..554e465a7 100644
--- a/internal/configuration/provider_test.go
+++ b/internal/configuration/provider_test.go
@@ -224,6 +224,39 @@ func TestShouldValidateAndRaiseErrorsOnBadConfiguration(t *testing.T) {
 	assert.EqualError(t, val.Errors()[1], "invalid configuration key 'logs_level' was replaced by 'log.level'")
 }
 
+func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
+	testReset()
+
+	val := schema.NewStructValidator()
+	keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_invalid.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
+
+	assert.NoError(t, err)
+
+	validator.ValidateKeys(keys, DefaultEnvPrefix, val)
+
+	require.Len(t, val.Errors(), 1)
+	assert.Len(t, val.Warnings(), 0)
+
+	assert.EqualError(t, val.Errors()[0], "error occurred during unmarshalling configuration: 1 error(s) decoding:\n\n* error decoding 'notifier.smtp.sender': could not parse 'admin' as a RFC5322 address: mail: missing '@' or angle-addr")
+}
+
+func TestShouldHandleErrInvalidatorWhenSMTPSenderBlank(t *testing.T) {
+	testReset()
+
+	val := schema.NewStructValidator()
+	keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_blank.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
+
+	assert.NoError(t, err)
+
+	validator.ValidateKeys(keys, DefaultEnvPrefix, val)
+
+	assert.Len(t, val.Errors(), 0)
+	assert.Len(t, val.Warnings(), 0)
+
+	assert.Equal(t, "", config.Notifier.SMTP.Sender.Name)
+	assert.Equal(t, "", config.Notifier.SMTP.Sender.Address)
+}
+
 func TestShouldNotReadConfigurationOnFSAccessDenied(t *testing.T) {
 	if runtime.GOOS == constWindows {
 		t.Skip("skipping test due to being on windows")
diff --git a/internal/configuration/test_resources/config_smtp_sender_blank.yml b/internal/configuration/test_resources/config_smtp_sender_blank.yml
new file mode 100644
index 000000000..a07eeb60a
--- /dev/null
+++ b/internal/configuration/test_resources/config_smtp_sender_blank.yml
@@ -0,0 +1,126 @@
+---
+default_redirection_url: https://home.example.com:8080/
+
+server:
+  host: 127.0.0.1
+  port: 9091
+
+log:
+  level: debug
+
+totp:
+  issuer: authelia.com
+
+duo_api:
+  hostname: api-123456789.example.com
+  integration_key: ABCDEF
+
+authentication_backend:
+  ldap:
+    url: ldap://127.0.0.1
+    base_dn: dc=example,dc=com
+    username_attribute: uid
+    additional_users_dn: ou=users
+    users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
+    additional_groups_dn: ou=groups
+    groups_filter: (&(member={dn})(objectClass=groupOfNames))
+    group_name_attribute: cn
+    mail_attribute: mail
+    user: cn=admin,dc=example,dc=com
+
+access_control:
+  default_policy: deny
+
+  rules:
+    # Rules applied to everyone
+    - domain: public.example.com
+      policy: bypass
+
+    - domain: secure.example.com
+      policy: one_factor
+      # Network based rule, if not provided any network matches.
+      networks:
+        - 192.168.1.0/24
+    - domain: secure.example.com
+      policy: two_factor
+
+    - domain: [singlefactor.example.com, onefactor.example.com]
+      policy: one_factor
+
+    # Rules applied to 'admins' group
+    - domain: "mx2.mail.example.com"
+      subject: "group:admins"
+      policy: deny
+    - domain: "*.example.com"
+      subject: "group:admins"
+      policy: two_factor
+
+    # Rules applied to 'dev' group
+    - domain: dev.example.com
+      resources:
+        - "^/groups/dev/.*$"
+      subject: "group:dev"
+      policy: two_factor
+
+    # Rules applied to user 'john'
+    - domain: dev.example.com
+      resources:
+        - "^/users/john/.*$"
+      subject: "user:john"
+      policy: two_factor
+
+    # Rules applied to 'dev' group and user 'john'
+    - domain: dev.example.com
+      resources:
+        - "^/deny-all.*$"
+      subject: ["group:dev", "user:john"]
+      policy: deny
+
+    # Rules applied to user 'harry'
+    - domain: dev.example.com
+      resources:
+        - "^/users/harry/.*$"
+      subject: "user:harry"
+      policy: two_factor
+
+    # Rules applied to user 'bob'
+    - domain: "*.mail.example.com"
+      subject: "user:bob"
+      policy: two_factor
+    - domain: "dev.example.com"
+      resources:
+        - "^/users/bob/.*$"
+      subject: "user:bob"
+      policy: two_factor
+
+session:
+  name: authelia_session
+  expiration: 3600000  # 1 hour
+  inactivity: 300000  # 5 minutes
+  domain: example.com
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    high_availability:
+      sentinel_name: test
+
+regulation:
+  max_retries: 3
+  find_time: 120
+  ban_time: 300
+
+storage:
+  mysql:
+    host: 127.0.0.1
+    port: 3306
+    database: authelia
+    username: authelia
+
+notifier:
+  smtp:
+    username: test
+    host: 127.0.0.1
+    port: 1025
+    sender: ""
+    disable_require_tls: true
+...
diff --git a/internal/configuration/test_resources/config_smtp_sender_invalid.yml b/internal/configuration/test_resources/config_smtp_sender_invalid.yml
new file mode 100644
index 000000000..255c01bb1
--- /dev/null
+++ b/internal/configuration/test_resources/config_smtp_sender_invalid.yml
@@ -0,0 +1,126 @@
+---
+default_redirection_url: https://home.example.com:8080/
+
+server:
+  host: 127.0.0.1
+  port: 9091
+
+log:
+  level: debug
+
+totp:
+  issuer: authelia.com
+
+duo_api:
+  hostname: api-123456789.example.com
+  integration_key: ABCDEF
+
+authentication_backend:
+  ldap:
+    url: ldap://127.0.0.1
+    base_dn: dc=example,dc=com
+    username_attribute: uid
+    additional_users_dn: ou=users
+    users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
+    additional_groups_dn: ou=groups
+    groups_filter: (&(member={dn})(objectClass=groupOfNames))
+    group_name_attribute: cn
+    mail_attribute: mail
+    user: cn=admin,dc=example,dc=com
+
+access_control:
+  default_policy: deny
+
+  rules:
+    # Rules applied to everyone
+    - domain: public.example.com
+      policy: bypass
+
+    - domain: secure.example.com
+      policy: one_factor
+      # Network based rule, if not provided any network matches.
+      networks:
+        - 192.168.1.0/24
+    - domain: secure.example.com
+      policy: two_factor
+
+    - domain: [singlefactor.example.com, onefactor.example.com]
+      policy: one_factor
+
+    # Rules applied to 'admins' group
+    - domain: "mx2.mail.example.com"
+      subject: "group:admins"
+      policy: deny
+    - domain: "*.example.com"
+      subject: "group:admins"
+      policy: two_factor
+
+    # Rules applied to 'dev' group
+    - domain: dev.example.com
+      resources:
+        - "^/groups/dev/.*$"
+      subject: "group:dev"
+      policy: two_factor
+
+    # Rules applied to user 'john'
+    - domain: dev.example.com
+      resources:
+        - "^/users/john/.*$"
+      subject: "user:john"
+      policy: two_factor
+
+    # Rules applied to 'dev' group and user 'john'
+    - domain: dev.example.com
+      resources:
+        - "^/deny-all.*$"
+      subject: ["group:dev", "user:john"]
+      policy: deny
+
+    # Rules applied to user 'harry'
+    - domain: dev.example.com
+      resources:
+        - "^/users/harry/.*$"
+      subject: "user:harry"
+      policy: two_factor
+
+    # Rules applied to user 'bob'
+    - domain: "*.mail.example.com"
+      subject: "user:bob"
+      policy: two_factor
+    - domain: "dev.example.com"
+      resources:
+        - "^/users/bob/.*$"
+      subject: "user:bob"
+      policy: two_factor
+
+session:
+  name: authelia_session
+  expiration: 3600000  # 1 hour
+  inactivity: 300000  # 5 minutes
+  domain: example.com
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    high_availability:
+      sentinel_name: test
+
+regulation:
+  max_retries: 3
+  find_time: 120
+  ban_time: 300
+
+storage:
+  mysql:
+    host: 127.0.0.1
+    port: 3306
+    database: authelia
+    username: authelia
+
+notifier:
+  smtp:
+    username: test
+    host: 127.0.0.1
+    port: 1025
+    sender: admin
+    disable_require_tls: true
+...
diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go
index 0a48aa947..1af7219a8 100644
--- a/internal/configuration/validator/notifier_test.go
+++ b/internal/configuration/validator/notifier_test.go
@@ -2,6 +2,7 @@ package validator
 
 import (
 	"fmt"
+	"net/mail"
 	"testing"
 
 	"github.com/stretchr/testify/suite"
@@ -20,12 +21,16 @@ func (suite *NotifierSuite) SetupTest() {
 	suite.configuration.SMTP = &schema.SMTPNotifierConfiguration{
 		Username: "john",
 		Password: "password",
-		Sender:   "admin@example.com",
+		Sender:   mail.Address{Name: "Authelia", Address: "authelia@example.com"},
 		Host:     "example.com",
 		Port:     25,
 	}
+	suite.configuration.FileSystem = nil
 }
 
+/*
+	Common Tests.
+*/
 func (suite *NotifierSuite) TestShouldEnsureAtLeastSMTPOrFilesystemIsProvided() {
 	ValidateNotifier(&suite.configuration, suite.validator)
 
@@ -63,29 +68,37 @@ func (suite *NotifierSuite) TestShouldEnsureEitherSMTPOrFilesystemIsProvided() {
 	suite.Assert().EqualError(suite.validator.Errors()[0], errFmtNotifierMultipleConfigured)
 }
 
-func (suite *NotifierSuite) TestShouldEnsureFilenameOfFilesystemNotifierIsProvided() {
-	suite.configuration.SMTP = nil
-	suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
-		Filename: "test",
-	}
+/*
+	SMTP Tests.
+*/
+func (suite *NotifierSuite) TestSMTPShouldSetTLSDefaults() {
 	ValidateNotifier(&suite.configuration, suite.validator)
 
 	suite.Assert().False(suite.validator.HasWarnings())
 	suite.Assert().False(suite.validator.HasErrors())
 
-	suite.configuration.FileSystem.Filename = ""
+	suite.Assert().Equal("example.com", suite.configuration.SMTP.TLS.ServerName)
+	suite.Assert().Equal("TLS1.2", suite.configuration.SMTP.TLS.MinimumVersion)
+	suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
+}
+
+func (suite *NotifierSuite) TestSMTPShouldDefaultTLSServerNameToHost() {
+	suite.configuration.SMTP.Host = "google.com"
+	suite.configuration.SMTP.TLS = &schema.TLSConfig{
+		MinimumVersion: "TLS1.1",
+	}
 
 	ValidateNotifier(&suite.configuration, suite.validator)
 
 	suite.Assert().False(suite.validator.HasWarnings())
-	suite.Require().True(suite.validator.HasErrors())
-
-	suite.Assert().Len(suite.validator.Errors(), 1)
+	suite.Assert().False(suite.validator.HasErrors())
 
-	suite.Assert().EqualError(suite.validator.Errors()[0], errFmtNotifierFileSystemFileNameNotConfigured)
+	suite.Assert().Equal("google.com", suite.configuration.SMTP.TLS.ServerName)
+	suite.Assert().Equal("TLS1.1", suite.configuration.SMTP.TLS.MinimumVersion)
+	suite.Assert().False(suite.configuration.SMTP.TLS.SkipVerify)
 }
 
-func (suite *NotifierSuite) TestShouldEnsureHostAndPortOfSMTPNotifierAreProvided() {
+func (suite *NotifierSuite) TestSMTPShouldEnsureHostAndPortAreProvided() {
 	suite.configuration.FileSystem = nil
 	ValidateNotifier(&suite.configuration, suite.validator)
 
@@ -108,15 +121,33 @@ func (suite *NotifierSuite) TestShouldEnsureHostAndPortOfSMTPNotifierAreProvided
 	suite.Assert().EqualError(errors[1], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "port"))
 }
 
-func (suite *NotifierSuite) TestShouldEnsureSenderOfSMTPNotifierAreProvided() {
-	suite.configuration.FileSystem = nil
+func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() {
+	suite.configuration.SMTP.Sender = mail.Address{}
+
+	ValidateNotifier(&suite.configuration, suite.validator)
+
+	suite.Assert().False(suite.validator.HasWarnings())
+	suite.Require().True(suite.validator.HasErrors())
+
+	suite.Assert().Len(suite.validator.Errors(), 1)
+
+	suite.Assert().EqualError(suite.validator.Errors()[0], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "sender"))
+}
 
+/*
+	File Tests.
+*/
+func (suite *NotifierSuite) TestFileShouldEnsureFilenameIsProvided() {
+	suite.configuration.SMTP = nil
+	suite.configuration.FileSystem = &schema.FileSystemNotifierConfiguration{
+		Filename: "test",
+	}
 	ValidateNotifier(&suite.configuration, suite.validator)
 
 	suite.Assert().False(suite.validator.HasWarnings())
 	suite.Assert().False(suite.validator.HasErrors())
 
-	suite.configuration.SMTP.Sender = ""
+	suite.configuration.FileSystem.Filename = ""
 
 	ValidateNotifier(&suite.configuration, suite.validator)
 
@@ -125,7 +156,7 @@ func (suite *NotifierSuite) TestShouldEnsureSenderOfSMTPNotifierAreProvided() {
 
 	suite.Assert().Len(suite.validator.Errors(), 1)
 
-	suite.Assert().EqualError(suite.validator.Errors()[0], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "sender"))
+	suite.Assert().EqualError(suite.validator.Errors()[0], errFmtNotifierFileSystemFileNameNotConfigured)
 }
 
 func TestNotifierSuite(t *testing.T) {
diff --git a/internal/notification/smtp_notifier_test.go b/internal/notification/smtp_notifier_test.go
index f8d366b6d..fc2ae2f4d 100644
--- a/internal/notification/smtp_notifier_test.go
+++ b/internal/notification/smtp_notifier_test.go
@@ -7,24 +7,21 @@ import (
 	"github.com/stretchr/testify/assert"
 
 	"github.com/authelia/authelia/v4/internal/configuration/schema"
-	"github.com/authelia/authelia/v4/internal/configuration/validator"
 )
 
-func TestShouldConfigureSMTPNotifierWithTLS11AndDefaultHostname(t *testing.T) {
+func TestShouldConfigureSMTPNotifierWithTLS11(t *testing.T) {
 	config := &schema.NotifierConfiguration{
 		DisableStartupCheck: true,
 		SMTP: &schema.SMTPNotifierConfiguration{
 			Host: "smtp.example.com",
 			Port: 25,
 			TLS: &schema.TLSConfig{
+				ServerName:     "smtp.example.com",
 				MinimumVersion: "TLS1.1",
 			},
 		},
 	}
 
-	sv := schema.NewStructValidator()
-	validator.ValidateNotifier(config, sv)
-
 	notifier := NewSMTPNotifier(config.SMTP, nil)
 
 	assert.Equal(t, "smtp.example.com", notifier.tlsConfig.ServerName)
@@ -44,9 +41,6 @@ func TestShouldConfigureSMTPNotifierWithServerNameOverrideAndDefaultTLS12(t *tes
 		},
 	}
 
-	sv := schema.NewStructValidator()
-	validator.ValidateNotifier(config, sv)
-
 	notifier := NewSMTPNotifier(config.SMTP, nil)
 
 	assert.Equal(t, "smtp.golang.org", notifier.tlsConfig.ServerName)