Repository: caronc/apprise

Issue:
'Unparseable E-Mail URL' error for hostnames containing underscore
With underscore:
```bash
$ docker run --rm caronc/apprise:latest /usr/local/bin/apprise --body='hello world' \
  'mailto://smtp_service:25/?from=foo@localhost&to=bar@localhost'
2020-11-22 16:33:16,297 - ERROR - Unparseable E-Mail URL mailto://smtp_service:25/?from=foo@localhost&to=bar@localhost
2020-11-22 16:33:16,297 - ERROR - You must specify at least one server URL or populated configuration file.
```

Without underscore:
```bash
$ docker run --rm caronc/apprise:latest /usr/local/bin/apprise --body='hello world' \
  'mailto://smtpservice:25/?from=foo@localhost&to=bar@localhost'
2020-11-22 16:34:12,541 - INFO - Notifying 1 service(s) asynchronously.
```

Underscores in hostnames are completely legal, see https://stackoverflow.com/a/2183140/5116073

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=h1) Report
> Merging [#324](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=desc) (d047d85) into [master](https://codecov.io/gh/caronc/apprise/commit/2c2722f61f9f983827c8246943f0462098e5a0ed?el=desc) (2c2722f) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/caronc/apprise/pull/324/graphs/tree.svg?width=650&height=150&src=pr&token=skAfz96t81)](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master      #324   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           85        85           
  Lines        10626     10626           
  Branches      1772      1772           
=========================================
  Hits         10626     10626           
```


| [Impacted Files](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [apprise/utils.py](https://codecov.io/gh/caronc/apprise/pull/324/diff?src=pr&el=tree#diff-YXBwcmlzZS91dGlscy5weQ==) | `100.00% <ø> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=footer). Last update [2c2722f...d047d85](https://codecov.io/gh/caronc/apprise/pull/324?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

caronc: Great work; thank you very much for the PR! :tada: :+1: 

Patch:
diff --git a/.gitignore b/.gitignore
index 11f190a..0315c5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,8 @@ target/
 
 #PyCharm
 .idea
+
+#PyDev (Eclipse)
+.project
+.pydevproject
+.settings
diff --git a/apprise/utils.py b/apprise/utils.py
index 21f2c49..4b4833b 100644
--- a/apprise/utils.py
+++ b/apprise/utils.py
@@ -120,9 +120,9 @@ GET_EMAIL_RE = re.compile(
     r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+'
     r'(?:\.[a-z0-9$%+=_~-]+)'
     r'*)@(?P<domain>('
-    r'(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+'
-    r'[a-z0-9](?:[a-z0-9-]*[a-z0-9]))|'
-    r'[a-z0-9][a-z0-9-]{5,})))'
+    r'(?:[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?\.)+'
+    r'[a-z0-9](?:[a-z0-9_-]*[a-z0-9]))|'
+    r'[a-z0-9][a-z0-9_-]{5,})))'
     r'\s*>?', re.IGNORECASE)
 
 # Regular expression used to extract a phone number
@@ -232,9 +232,12 @@ def is_hostname(hostname, ipv4=True, ipv6=True):
     # - Hostnames can ony be comprised of alpha-numeric characters and the
     #   hyphen (-) character.
     # - Hostnames can not start with the hyphen (-) character.
+    # - as a workaround for https://github.com/docker/compose/issues/229 to
+    #   being able to address services in other stacks, we also allow
+    #   underscores in hostnames
     # - labels can not exceed 63 characters
     allowed = re.compile(
-        r'(?!-)[a-z0-9][a-z0-9-]{1,62}(?<!-)$',
+        r'^[a-z0-9][a-z0-9_-]{1,62}(?<!-)$',
         re.IGNORECASE,
     )
 


Test patch:
diff --git a/test/test_utils.py b/test/test_utils.py
index b187da0..37c2ba7 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -532,6 +532,8 @@ def test_is_hostname():
     assert utils.is_hostname('yahoo.ca.') == 'yahoo.ca'
     assert utils.is_hostname('valid-dashes-in-host.ca') == \
         'valid-dashes-in-host.ca'
+    assert utils.is_hostname('valid-underscores_in_host.ca') == \
+        'valid-underscores_in_host.ca'
 
     # Invalid Hostnames
     assert utils.is_hostname('-hostname.that.starts.with.a.dash') is False
@@ -539,7 +541,6 @@ def test_is_hostname():
     assert utils.is_hostname('    spaces   ') is False
     assert utils.is_hostname('       ') is False
     assert utils.is_hostname('') is False
-    assert utils.is_hostname('valid-underscores_in_host.ca') is False
 
     # Valid IPv4 Addresses
     assert utils.is_hostname('127.0.0.1') == '127.0.0.1'
@@ -625,6 +626,14 @@ def test_is_email():
     assert 'test' == results['user']
     assert '' == results['label']
 
+    results = utils.is_email('test@my-valid_host.com')
+    assert '' == results['name']
+    assert 'test@my-valid_host.com' == results['email']
+    assert 'test@my-valid_host.com' == results['full_email']
+    assert 'my-valid_host.com' == results['domain']
+    assert 'test' == results['user']
+    assert '' == results['label']
+
     results = utils.is_email('tag+test@gmail.com')
     assert '' == results['name']
     assert 'test@gmail.com' == results['email']