Repository: mandarons/icloud-drive-docker (python)

Issue:
[BUG] Unable to authenticate SMTP due to inability to set username
**Describe the bug**
Arguably in-between a bug/feature request but I'd class it as a problem with existing functionality rather than a request for new functionality, so I'm choosing bug! I'm not able to use SMTP notifications because my SMTP provider pre-generates usernames to be a random alphanumeric string rather than an email address, and the config.yaml file only allows me to input sender/receiver email addresses and not the username for smtp auth.

**To Reproduce**
Steps to reproduce the behavior: Try to use AWS SES or any other similar SMTP provider with this project

**Expected behavior**
I'm able to customise the SMTP username to be different than the sender email address.

Below is what I'd expect the SMTP section of the config.yaml file to look like:

```
email: "example@example.com"
# username: "" # Uncomment this if your SMTP username is different than your sender address
# to: "email@domain.com" # Uncomment this if you'd like to send notifications to a different email address than your sender address
password: ""
host: ""
port: 587
# no_tls: true # Uncomment this if your SMTP provider doesn't support TLS
```

Pull request:
Support for username based email services
Closes #115

Patch:
diff --git a/config.yaml b/config.yaml
index 547956b4..83697549 100644
--- a/config.yaml
+++ b/config.yaml
@@ -14,6 +14,8 @@ app:
   smtp:
   # If you want to receive email notifications about expired/missing 2FA credentials then uncomment
   # email: "sender@test.com"
+  # Uncomment this if your SMTP username is different than your sender address (for services like AWS SES)
+  # username: ""
   # default to is same as email above
   # to: "receiver@test.com"
   # password:
@@ -42,7 +44,7 @@ drive:
 photos:
   destination: "photos"
   remove_obsolete: false
-  sync_inteval: 500
+  sync_interval: 500
   filters:
     albums:
       - "album 1"
diff --git a/src/config_parser.py b/src/config_parser.py
index cb420be8..ef89d416 100644
--- a/src/config_parser.py
+++ b/src/config_parser.py
@@ -122,6 +122,15 @@ def get_smtp_email(config):
     return email
 
 
+def get_smtp_username(config):
+    """Return smtp username from the config, if set."""
+    username = None
+    config_path = ["app", "smtp", "username"]
+    if traverse_config_path(config=config, config_path=config_path):
+        username = get_config_value(config=config, config_path=config_path)
+    return username
+
+
 def get_smtp_to_email(config):
     """Return smtp to email from config."""
     to_email = None
diff --git a/src/notify.py b/src/notify.py
index 57a57697..53f9c32e 100644
--- a/src/notify.py
+++ b/src/notify.py
@@ -14,6 +14,7 @@ def send(config, last_send=None, dry_run=False):
     host = config_parser.get_smtp_host(config=config)
     port = config_parser.get_smtp_port(config=config)
     no_tls = config_parser.get_smtp_no_tls(config=config)
+    username = config_parser.get_smtp_username(config=config)
     password = config_parser.get_smtp_password(config=config)
 
     if last_send and last_send > datetime.datetime.now() - datetime.timedelta(hours=24):
@@ -30,7 +31,10 @@ def send(config, last_send=None, dry_run=False):
                     smtp.starttls()
 
                 if password:
-                    smtp.login(email, password)
+                    if username:
+                        smtp.login(username, password)
+                    else:
+                        smtp.login(email, password)
 
                 msg = build_message(email)
 


Test patch:
diff --git a/tests/data/test_config.yaml b/tests/data/test_config.yaml
index 6b808b2f..a63a5bc5 100644
--- a/tests/data/test_config.yaml
+++ b/tests/data/test_config.yaml
@@ -14,6 +14,8 @@ app:
   smtp:
   # If you want to recieve email notifications about expired/missing 2FA credentials then uncomment
   # email: sender@test.com
+  # Uncomment this if your SMTP username is different than your sender address (for services like AWS SES)
+  # username: ""
   # default to is same as email above
   # to: receiver@test.com
   # password:
diff --git a/tests/test_config_parser.py b/tests/test_config_parser.py
index 38b4d232..e1580d77 100644
--- a/tests/test_config_parser.py
+++ b/tests/test_config_parser.py
@@ -211,6 +211,17 @@ class TestConfigParser(unittest.TestCase):
             config["app"]["smtp"]["email"], config_parser.get_smtp_email(config=config)
         )
 
+    def test_get_smtp_username(self):
+        """If present, get smtp username else None."""
+        config = {"app": {"smtp": {"username": "smtp-username"}}}
+        self.assertEqual(
+            config["app"]["smtp"]["username"],
+            config_parser.get_smtp_username(config=config),
+        )
+
+        del config["app"]["smtp"]["username"]
+        self.assertIsNone(config_parser.get_smtp_username(config=config))
+
     def test_smtp_email_none_config(self):
         """None config."""
         self.assertIsNone(config_parser.get_smtp_email(config=None))
diff --git a/tests/test_notify.py b/tests/test_notify.py
index b2b18b06..d9bdab45 100644
--- a/tests/test_notify.py
+++ b/tests/test_notify.py
@@ -81,6 +81,34 @@ class TestNotify(unittest.TestCase):
                 instance.sendmail.mock_calls[0][2]["msg"],
             )
 
+    def test_send_with_username(self):
+        """Test for email send."""
+        with patch("smtplib.SMTP") as smtp:
+            self.config["app"]["smtp"]["username"] = "smtp-username"
+            notify.send(self.config)
+
+            instance = smtp.return_value
+
+            # verify that sendmail() was called
+            self.assertTrue(instance.sendmail.called)
+            self.assertEqual(instance.sendmail.call_count, 1)
+
+            # verify that the correct email is being sent to sendmail()
+            self.assertEqual(
+                config_parser.get_smtp_email(config=self.config),
+                instance.sendmail.mock_calls[0][2]["from_addr"],
+            )
+            self.assertEqual(
+                config_parser.get_smtp_to_email(config=self.config),
+                instance.sendmail.mock_calls[0][2]["to_addrs"],
+            )
+
+            # verify that the message was passed to sendmail()
+            self.assertIn(
+                "Subject: icloud-docker: Two step authentication required",
+                instance.sendmail.mock_calls[0][2]["msg"],
+            )
+
     def test_send_fail(self):
         """Test for failed send."""
         with patch("smtplib.SMTP") as smtp: