Added multiple e-mail support - fixed documentation - version 0.9

This commit is contained in:
Stefano Marinelli 2023-10-21 10:07:15 +02:00
parent 2577a52c1a
commit 98b14dbf8f
3 changed files with 116 additions and 64 deletions

View file

@ -1,16 +1,16 @@
""" """
NotiMail NotiMail
Version: 0.8 - Alpha Version: 0.9 - Alpha
Author: Stefano Marinelli <stefano@dragas.it> Author: Stefano Marinelli <stefano@dragas.it>
License: BSD 3-Clause License License: BSD 3-Clause License
NotiMail is a script designed to monitor an email inbox using the IMAP IDLE feature NotiMail is a script designed to monitor one or more email inbox(es) using the IMAP IDLE feature
and send notifications via HTTP POST requests when a new email arrives. This version includes and send notifications via HTTP POST requests when a new email arrives. This version includes
additional features to store processed email UIDs in a SQLite3 database and ensure they are not additional features to store processed email UIDs in a SQLite3 database and ensure they are not
processed repeatedly. processed repeatedly.
The script uses: The script uses:
- IMAP to connect to an email server - IMAP to connect to one or more email server(s)
- IDLE mode to wait for new emails - IDLE mode to wait for new emails
- Sends a notification containing the sender and subject of the new email upon receipt - Sends a notification containing the sender and subject of the new email upon receipt
- Maintains a SQLite database to keep track of processed emails - Maintains a SQLite database to keep track of processed emails
@ -24,6 +24,7 @@ Python Dependencies:
- sqlite3: For database operations. - sqlite3: For database operations.
- datetime: For date and time operations. - datetime: For date and time operations.
- signal, sys: For handling script shutdown and signals. - signal, sys: For handling script shutdown and signals.
- threading: to deal with multiple inboxes
- BytesParser from email.parser: For parsing raw email data. - BytesParser from email.parser: For parsing raw email data.
Configuration: Configuration:
@ -70,6 +71,7 @@ import datetime
import signal import signal
import sys import sys
import logging import logging
import threading
from email import policy from email import policy
from email.parser import BytesParser from email.parser import BytesParser
@ -111,11 +113,13 @@ class DatabaseHandler:
def close(self): def close(self):
self.connection.close() self.connection.close()
# Create a global database handler
db_handler = DatabaseHandler()
class EmailProcessor: class EmailProcessor:
def __init__(self, mail): def __init__(self, mail):
self.mail = mail self.mail = mail
self.db_handler = DatabaseHandler() self.db_handler = db_handler # Use the global db_handler
def fetch_unseen_emails(self): def fetch_unseen_emails(self):
status, messages = self.mail.uid('search', None, "UNSEEN") status, messages = self.mail.uid('search', None, "UNSEEN")
@ -307,62 +311,27 @@ class IMAPHandler:
processor = EmailProcessor(self.mail) processor = EmailProcessor(self.mail)
processor.process() processor.process()
class MultiIMAPHandler:
def __init__(self, accounts):
self.accounts = accounts
self.handlers = [IMAPHandler(account['Host'], account['EmailUser'], account['EmailPass']) for account in accounts]
# Load configurations from config.ini def run(self):
config = configparser.ConfigParser() threads = []
config.read('config.ini') for handler in self.handlers:
thread = threading.Thread(target=self.monitor_account, args=(handler,))
thread.daemon = True # Set thread as daemon
threads.append(thread)
thread.start()
email_user = config['EMAIL']['EmailUser'] for thread in threads:
email_pass = config['EMAIL']['EmailPass'] thread.join()
host = config['EMAIL']['Host']
# Example: Creating notification providers based on configuration
providers = []
if 'NTFY' in config:
ntfy_urls = [config['NTFY'][url_key] for url_key in config['NTFY']]
providers.append(NTFYNotificationProvider(ntfy_urls))
if 'PUSHOVER' in config:
api_token = config['PUSHOVER']['ApiToken']
user_key = config['PUSHOVER']['UserKey']
providers.append(PushoverNotificationProvider(api_token, user_key))
if 'GOTIFY' in config:
gotify_url = config['GOTIFY']['Url']
gotify_token = config['GOTIFY']['Token']
providers.append(GotifyNotificationProvider(gotify_url, gotify_token))
# Initialize Notifier with providers
notifier = Notifier(providers)
# Set a global timeout for all socket operations @staticmethod
socket.setdefaulttimeout(600) # e.g., 600 seconds or 10 minutes def monitor_account(handler):
print(f"Monitoring {handler.email_user}")
def shutdown_handler(signum, frame): logging.info(f"Monitoring {handler.email_user}")
print("Shutdown signal received. Cleaning up...")
logging.info(f"Shutdown signal received. Cleaning up...")
try:
handler.mail.logout()
except:
pass
processor.db_handler.close()
print("Cleanup complete. Exiting.")
logging.info(f"Cleanup complete. Exiting.")
sys.exit(0)
# Register the signal handlers
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)
print("Script started. Press Ctrl+C to stop it anytime.")
logging.info(f"Script started. Press Ctrl+C to stop it anytime.")
handler = IMAPHandler(host, email_user, email_pass)
processor = EmailProcessor(None) # Creating an instance for graceful shutdown handling
try:
while True:
try: try:
handler.connect() handler.connect()
while True: while True:
@ -370,17 +339,89 @@ try:
handler.process_emails() handler.process_emails()
except ConnectionAbortedError as e: except ConnectionAbortedError as e:
print(str(e)) print(str(e))
time.sleep(30) # wait for 30 seconds before trying to reconnect time.sleep(30)
except Exception as e: except Exception as e:
print(f"An unexpected error occurred: {str(e)}") print(f"An unexpected error occurred: {str(e)}")
logging.error(f"An unexpected error occurred: {str(e)}") logging.error(f"An unexpected error occurred: {str(e)}")
notifier.send_notification("Script Error", f"An unexpected error occurred: {str(e)}") notifier.send_notification("Script Error", f"An unexpected error occurred: {str(e)}")
finally:
def shutdown_handler(signum, frame):
print("Shutdown signal received. Cleaning up...")
logging.info(f"Shutdown signal received. Cleaning up...")
try:
for handler in MultiIMAPHandler.handlers:
handler.mail.logout()
except:
pass
db_handler.close() # Use the global db_handler
print("Cleanup complete. Exiting.")
logging.info(f"Cleanup complete. Exiting.")
sys.exit(0)
def multi_account_main():
config = configparser.ConfigParser()
config.read('config.ini')
accounts = []
# Check for the old format [EMAIL] section
if 'EMAIL' in config.sections():
old_account = {
'EmailUser': config['EMAIL']['EmailUser'],
'EmailPass': config['EMAIL']['EmailPass'],
'Host': config['EMAIL']['Host']
}
accounts.append(old_account)
# Check for new format [EMAIL:accountX]
for section in config.sections():
if section.startswith("EMAIL:"):
account = {
'EmailUser': config[section]['EmailUser'],
'EmailPass': config[section]['EmailPass'],
'Host': config[section]['Host']
}
accounts.append(account)
providers = []
if 'NTFY' in config:
ntfy_urls = [config['NTFY'][url_key] for url_key in config['NTFY']]
providers.append(NTFYNotificationProvider(ntfy_urls))
if 'PUSHOVER' in config:
api_token = config['PUSHOVER']['ApiToken']
user_key = config['PUSHOVER']['UserKey']
providers.append(PushoverNotificationProvider(api_token, user_key))
if 'GOTIFY' in config:
gotify_url = config['GOTIFY']['Url']
gotify_token = config['GOTIFY']['Token']
providers.append(GotifyNotificationProvider(gotify_url, gotify_token))
global notifier
notifier = Notifier(providers)
socket.setdefaulttimeout(600)
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)
print("Script started. Press Ctrl+C to stop it anytime.")
logging.info(f"Script started. Press Ctrl+C to stop it anytime.")
multi_handler = MultiIMAPHandler(accounts)
multi_handler.run()
print("Logging out and closing the connection...") print("Logging out and closing the connection...")
logging.info(f"Logging out and closing the connection...") logging.info(f"Logging out and closing the connection...")
try: try:
handler.mail.logout() for handler in MultiIMAPHandler.handlers:
handler.mail.logout()
except: except:
pass pass
processor.db_handler.close() processor.db_handler.close()
if __name__ == "__main__":
multi_account_main()

View file

@ -1,14 +1,16 @@
# NotiMail 📧 # NotiMail 📧
**Version 0.9 is here, featuring support for monitoring multiple email accounts!**
**Development is ongoing, and the project is in the early alpha stage - things may break!** **Development is ongoing, and the project is in the early alpha stage - things may break!**
Stay connected without the constant drain on your battery. Introducing **NotiMail** - the future of server-side email notifications supporting multiple push providers! Stay connected without the constant drain on your battery. Introducing **NotiMail** - the future of server-side email notifications supporting multiple push providers and multiple email accounts!
Mobile devices often use IMAP IDLE, maintaining a persistent connection to ensure real-time email notifications. Such continuous connections rapidly consume battery power. The modern era demanded a smarter, more energy-efficient solution. Meet NotiMail. Mobile devices often use IMAP IDLE, maintaining a persistent connection to ensure real-time email notifications. Such continuous connections rapidly consume battery power. The modern era demanded a smarter, more energy-efficient solution. Meet NotiMail.
## Features 🌟 ## Features 🌟
- **Monitors Emails on the Server**: NotiMail checks for new, unseen emails without needing a constant client connection. - **Monitors Multiple Emails on the Server**: With version 0.9, NotiMail can now monitor multiple email accounts. Ensure you never miss an email regardless of which account it's sent to.
- **Processes and Notifies**: Once a new email is detected, NotiMail swiftly processes its details. - **Processes and Notifies**: Once a new email is detected, NotiMail swiftly processes its details.
@ -36,7 +38,7 @@ Mobile devices often use IMAP IDLE, maintaining a persistent connection to ensur
- **Efficient Operation**: Crafted for server-side operations, NotiMail guarantees a smooth, lightweight experience. - **Efficient Operation**: Crafted for server-side operations, NotiMail guarantees a smooth, lightweight experience.
- **Customizable Settings**: Through an intuitive `config.ini`, customize NotiMail to fit your needs. - **Customizable Settings**: Through an intuitive `config.ini`, customize NotiMail to fit your needs. With the new 0.9 version, configure multiple email accounts for monitoring.
- **Dependable Error Handling**: With robust mechanisms, NotiMail ensures you're notified of any hitches or anomalies. - **Dependable Error Handling**: With robust mechanisms, NotiMail ensures you're notified of any hitches or anomalies.
@ -111,7 +113,7 @@ bash
**5. Configure NotiMail:** **5. Configure NotiMail:**
Open the `config.ini` file in a text editor. Update the `[EMAIL]` section with your email credentials and host, and the configuration of one (or more) of the supported push providers. Open the `config.ini` file in a text editor. From version 0.9, you can configure multiple email accounts for monitoring by adding sections like `[EMAIL:account1]`, `[EMAIL:account2]`, etc. If you're upgrading from an earlier version, your old single `[EMAIL]` configuration is still supported. Also, update the configuration for one (or more) of the supported push providers.
**6. Run NotiMail:** **6. Run NotiMail:**
@ -132,4 +134,8 @@ bash
With that, you should have NotiMail up and running on your system! Enjoy a more efficient email notification experience. With that, you should have NotiMail up and running on your system! Enjoy a more efficient email notification experience.
### Changelog:
- **Version 0.9:**
- Introduced support for monitoring multiple email accounts. Configure multiple accounts in the `config.ini` using the format `[EMAIL:account1]`, `[EMAIL:account2]`, and so on.
- Maintained compatibility with the old single `[EMAIL]` configuration for a smooth upgrade path.

View file

@ -1,8 +1,13 @@
[EMAIL] [EMAIL:account1]
EmailUser = your@address.com EmailUser = your@address.com
EmailPass = YourPassword EmailPass = YourPassword
Host = mail.server.com Host = mail.server.com
#[EMAIL:account2]
#EmailUser = your@address.com
#EmailPass = YourPassword
#Host = mail.server.com
#If your provider is NTFY, uncomment the following lines and configure #If your provider is NTFY, uncomment the following lines and configure
#[NTFY] #[NTFY]
#Url1 = https://ntfy.sh/TOPIC1 #Url1 = https://ntfy.sh/TOPIC1