Google API Gmail LINE Notifications (7 Part Series)
1 Connecting to Gmail API with Python
2 Managing your Labels in Gmail
… 3 more parts…
3 Searching for and Getting Emails
4 Processing Email Contents
5 Processing Email – Being Selective
6 Processing Email – Single Script / Multiple Emails
7 Automation with Systemd & Code
Part 6 in a series of articles on implementing a notification system using Gmail and Line Bot
Greetings. In this article I will be going over the thought process of how I handled multiple emails in a single script.
In most cases there will only be a single email that needs to be processed at any given time. But to allow for more flexibility and to have a single place of control, and unfortunately complexity, I have decided to use a single script.
Some background
The services I receive email from have some well defined static attributes.
- Sending Address
- Subject Line
About once a month they will also send billing information using the same Sending Address which can be ignored by the script.
Because of this; the logic will be something along the lines of.
- Check the subject line. If it exist in the list of known subject lines process the email.
- If 1 true. Double check that the sending address is an approved/known sending address.
- If 1 is false. Log an issue (This should not occur due to the filtering which is being done within Gmail.)
- If 1 is true, but the sending address is false. Log the message.
- If both 1 and 2 are true. This is a valid notification email.
- Determine which notification is being handled using the Sender’s Address.
- Apply the correct regex expression(s) to get the information
- Send the notification and log its successful handling.
- Attach the notified label to the email.
Organization
constants.py
<span>SUB_1</span> <span>=</span> <span>"something"</span><span>SUB_2</span> <span>=</span> <span>"something"</span><span>SUBJECTS</span> <span>=</span> <span>[</span><span>SUB_1</span><span>,</span> <span>SUB_2</span><span>]</span><span>FROM_BUS</span> <span>=</span> <span>"<email address of sender>"</span><span>FROM_KIDZDUO</span> <span>=</span> <span>"<email address of sender>"</span><span>SUB_1</span> <span>=</span> <span>"something"</span> <span>SUB_2</span> <span>=</span> <span>"something"</span> <span>SUBJECTS</span> <span>=</span> <span>[</span><span>SUB_1</span><span>,</span> <span>SUB_2</span><span>]</span> <span>FROM_BUS</span> <span>=</span> <span>"<email address of sender>"</span> <span>FROM_KIDZDUO</span> <span>=</span> <span>"<email address of sender>"</span>SUB_1 = "something" SUB_2 = "something" SUBJECTS = [SUB_1, SUB_2] FROM_BUS = "<email address of sender>" FROM_KIDZDUO = "<email address of sender>"
Enter fullscreen mode Exit fullscreen mode
gmail.py
I have intentionally used a somewhat defensive coding style here.
The code look something like this
<span>def</span> <span>handle_each_email</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span> <span>-></span> <span>tuple</span><span>:</span><span>data</span> <span>=</span> <span>notifier</span> <span>=</span> <span>None</span><span>single_email</span> <span>=</span> <span>get_message</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span><span># Check the subject is an expected notification subject line </span> <span>subject</span> <span>=</span> <span>single_email</span><span>.</span><span>get</span><span>(</span><span>"subject"</span><span>)</span><span>if</span> <span>subject</span> <span>in</span> <span>constants</span><span>.</span><span>SUBJECTS</span><span>:</span><span># workout which notification we are dealing with and use the correct </span> <span># regular expression string </span> <span>sender</span> <span>=</span> <span>single_email</span><span>.</span><span>get</span><span>(</span><span>"from"</span><span>)</span><span>email_body</span> <span>=</span> <span>single_email</span><span>.</span><span>get_content</span><span>()</span><span>if</span> <span>sender</span> <span>==</span> <span>constants</span><span>.</span><span>FROM_BUS</span><span>:</span><span>data</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span><span>patterns</span><span>.</span><span>BUS_DATA</span><span>)</span><span>datetime</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span><span>patterns</span><span>.</span><span>BUS_DATE_TIME</span><span>)</span><span># Merge data and datetime into a single dictionary </span> <span>data</span><span>.</span><span>update</span><span>(</span><span>datetime</span><span>)</span><span>notifier</span> <span>=</span> <span>"BUS"</span><span>elif</span> <span>sender</span> <span>==</span> <span>constants</span><span>.</span><span>FROM_KIDZDUO</span><span>:</span><span>data</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span><span>patterns</span><span>.</span><span>KIDZDUO_ENTEREXIT</span><span>)</span><span>notifier</span> <span>=</span> <span>"KIDZDUO"</span><span>else</span><span>:</span><span># This needs to be logged. Means failed to match sender. </span> <span># if notifier is None: </span> <span>logger</span><span>.</span><span>warning</span><span>(</span><span>f</span><span>"Failed to process message_id: </span><span>{</span><span>message_id</span><span>}</span><span> "</span><span>f</span><span>"Matched Subject: </span><span>{</span><span>subject</span><span>}</span><span> "</span><span>f</span><span>"Sender not matched: </span><span>{</span><span>sender</span><span>}</span><span>"</span><span>)</span><span>pass</span><span>return</span> <span>notifier</span><span>,</span> <span>data</span><span>else</span><span>:</span><span># Not an expected subject line. Ignore this email </span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>"This is not a notifcation."</span><span>)</span><span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"sender: </span><span>{</span><span>sender</span><span>}</span><span>\n\t</span><span>"</span><span>)</span><span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"subject: </span><span>{</span><span>subject</span><span>}</span><span>"</span><span>)</span><span>return</span> <span>notifier</span><span>,</span> <span>data</span><span>def</span> <span>handle_each_email</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span> <span>-></span> <span>tuple</span><span>:</span> <span>data</span> <span>=</span> <span>notifier</span> <span>=</span> <span>None</span> <span>single_email</span> <span>=</span> <span>get_message</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span> <span># Check the subject is an expected notification subject line </span> <span>subject</span> <span>=</span> <span>single_email</span><span>.</span><span>get</span><span>(</span><span>"subject"</span><span>)</span> <span>if</span> <span>subject</span> <span>in</span> <span>constants</span><span>.</span><span>SUBJECTS</span><span>:</span> <span># workout which notification we are dealing with and use the correct </span> <span># regular expression string </span> <span>sender</span> <span>=</span> <span>single_email</span><span>.</span><span>get</span><span>(</span><span>"from"</span><span>)</span> <span>email_body</span> <span>=</span> <span>single_email</span><span>.</span><span>get_content</span><span>()</span> <span>if</span> <span>sender</span> <span>==</span> <span>constants</span><span>.</span><span>FROM_BUS</span><span>:</span> <span>data</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span> <span>patterns</span><span>.</span><span>BUS_DATA</span><span>)</span> <span>datetime</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span> <span>patterns</span><span>.</span><span>BUS_DATE_TIME</span><span>)</span> <span># Merge data and datetime into a single dictionary </span> <span>data</span><span>.</span><span>update</span><span>(</span><span>datetime</span><span>)</span> <span>notifier</span> <span>=</span> <span>"BUS"</span> <span>elif</span> <span>sender</span> <span>==</span> <span>constants</span><span>.</span><span>FROM_KIDZDUO</span><span>:</span> <span>data</span> <span>=</span> <span>patterns</span><span>.</span><span>findMatches</span><span>(</span><span>email_body</span><span>,</span> <span>patterns</span><span>.</span><span>KIDZDUO_ENTEREXIT</span><span>)</span> <span>notifier</span> <span>=</span> <span>"KIDZDUO"</span> <span>else</span><span>:</span> <span># This needs to be logged. Means failed to match sender. </span> <span># if notifier is None: </span> <span>logger</span><span>.</span><span>warning</span><span>(</span><span>f</span><span>"Failed to process message_id: </span><span>{</span><span>message_id</span><span>}</span><span> "</span> <span>f</span><span>"Matched Subject: </span><span>{</span><span>subject</span><span>}</span><span> "</span> <span>f</span><span>"Sender not matched: </span><span>{</span><span>sender</span><span>}</span><span>"</span><span>)</span> <span>pass</span> <span>return</span> <span>notifier</span><span>,</span> <span>data</span> <span>else</span><span>:</span> <span># Not an expected subject line. Ignore this email </span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>"This is not a notifcation."</span><span>)</span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"sender: </span><span>{</span><span>sender</span><span>}</span><span>\n\t</span><span>"</span><span>)</span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"subject: </span><span>{</span><span>subject</span><span>}</span><span>"</span><span>)</span> <span>return</span> <span>notifier</span><span>,</span> <span>data</span>def handle_each_email(service, message_id, logger) -> tuple: data = notifier = None single_email = get_message(service, message_id, logger) # Check the subject is an expected notification subject line subject = single_email.get("subject") if subject in constants.SUBJECTS: # workout which notification we are dealing with and use the correct # regular expression string sender = single_email.get("from") email_body = single_email.get_content() if sender == constants.FROM_BUS: data = patterns.findMatches(email_body, patterns.BUS_DATA) datetime = patterns.findMatches(email_body, patterns.BUS_DATE_TIME) # Merge data and datetime into a single dictionary data.update(datetime) notifier = "BUS" elif sender == constants.FROM_KIDZDUO: data = patterns.findMatches(email_body, patterns.KIDZDUO_ENTEREXIT) notifier = "KIDZDUO" else: # This needs to be logged. Means failed to match sender. # if notifier is None: logger.warning(f"Failed to process message_id: {message_id} " f"Matched Subject: {subject} " f"Sender not matched: {sender}") pass return notifier, data else: # Not an expected subject line. Ignore this email logger.info("This is not a notifcation.") logger.info(f"sender: {sender}\n\t") logger.info(f"subject: {subject}") return notifier, data
Enter fullscreen mode Exit fullscreen mode
The break down
- First we set data and notifier to None
- Get the full email
- Get the subject line
- Check that the subject line is one of the expect subject strings
- Get the sending address and email body
- Check who the sender is:
- If it’s the bus company. set data using the bus regex and set notifier to “BUS”
- If it’s Kidsduo. Set the data using the kidzduo regex and set nofifer to “KIDZDUO”
- Otherwise log the warning and return notifier and data
- If item 4 above is false, log an info message and return notifier and data both of which should be None
Main loop
Let’s see how we will process this in a batch form if needed.
<span>for</span> <span>message_id</span> <span>in</span> <span>list_of_message_ids</span><span>:</span><span>processed</span> <span>=</span> <span>False</span><span>notifier</span><span>,</span> <span>data</span> <span>=</span> <span>handle_each_email</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span><span># Notifier tells us how the data dict is structured </span> <span>if</span> <span>notifier</span> <span>==</span> <span>"BUS"</span><span>:</span><span>logger</span><span>.</span><span>debug</span><span>(</span><span>"Bus"</span><span>)</span><span># Your notification / bot code here </span> <span>processed</span> <span>=</span> <span>True</span><span>elif</span> <span>notifier</span> <span>==</span> <span>"KIDZDUO"</span><span>:</span><span>logger</span><span>.</span><span>debug</span><span>(</span><span>"KidsDuo"</span><span>)</span><span># Your notification / bot code here </span> <span>processed</span> <span>=</span> <span>True</span><span>elif</span> <span>notifier</span> <span>is</span> <span>None</span> <span>and</span> <span>data</span> <span>is</span> <span>not</span> <span>None</span><span>:</span><span>logger</span><span>.</span><span>warning</span><span>(</span><span>f</span><span>"Subject matched but From was not matched"</span><span>)</span><span>elif</span> <span>notifier</span> <span>is</span> <span>None</span> <span>and</span> <span>data</span> <span>is</span> <span>None</span><span>:</span><span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"Non-Notification email from expected sender"</span><span>)</span><span>else</span><span>:</span><span># We should not get here. But log it. </span> <span>logger</span><span>.</span><span>warning</span><span>(</span><span>f</span><span>"Something went wrong. Unexpected match."</span><span>f</span><span>"Don't know how to handle data."</span><span>)</span><span>if</span> <span>processed</span><span>:</span><span># Mail was processed. Add label so it's not processed again </span> <span># Gmail only allows for the shortest time interval of one day. </span> <span>logger</span><span>.</span><span>debug</span><span>(</span><span>"adding label"</span><span>)</span><span>add_label_to_message</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>secrets</span><span>.</span><span>LABEL_ID</span><span>)</span><span># End of the program </span><span>logger</span><span>.</span><span>info</span><span>(</span><span>"Ending cleanly"</span><span>)</span><span>for</span> <span>message_id</span> <span>in</span> <span>list_of_message_ids</span><span>:</span> <span>processed</span> <span>=</span> <span>False</span> <span>notifier</span><span>,</span> <span>data</span> <span>=</span> <span>handle_each_email</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>logger</span><span>)</span> <span># Notifier tells us how the data dict is structured </span> <span>if</span> <span>notifier</span> <span>==</span> <span>"BUS"</span><span>:</span> <span>logger</span><span>.</span><span>debug</span><span>(</span><span>"Bus"</span><span>)</span> <span># Your notification / bot code here </span> <span>processed</span> <span>=</span> <span>True</span> <span>elif</span> <span>notifier</span> <span>==</span> <span>"KIDZDUO"</span><span>:</span> <span>logger</span><span>.</span><span>debug</span><span>(</span><span>"KidsDuo"</span><span>)</span> <span># Your notification / bot code here </span> <span>processed</span> <span>=</span> <span>True</span> <span>elif</span> <span>notifier</span> <span>is</span> <span>None</span> <span>and</span> <span>data</span> <span>is</span> <span>not</span> <span>None</span><span>:</span> <span>logger</span><span>.</span><span>warning</span><span>(</span><span>f</span><span>"Subject matched but From was not matched"</span><span>)</span> <span>elif</span> <span>notifier</span> <span>is</span> <span>None</span> <span>and</span> <span>data</span> <span>is</span> <span>None</span><span>:</span> <span>logger</span><span>.</span><span>info</span><span>(</span><span>f</span><span>"Non-Notification email from expected sender"</span><span>)</span> <span>else</span><span>:</span> <span># We should not get here. But log it. </span> <span>logger</span><span>.</span><span>warning</span><span>(</span> <span>f</span><span>"Something went wrong. Unexpected match."</span> <span>f</span><span>"Don't know how to handle data."</span> <span>)</span> <span>if</span> <span>processed</span><span>:</span> <span># Mail was processed. Add label so it's not processed again </span> <span># Gmail only allows for the shortest time interval of one day. </span> <span>logger</span><span>.</span><span>debug</span><span>(</span><span>"adding label"</span><span>)</span> <span>add_label_to_message</span><span>(</span><span>service</span><span>,</span> <span>message_id</span><span>,</span> <span>secrets</span><span>.</span><span>LABEL_ID</span><span>)</span> <span># End of the program </span><span>logger</span><span>.</span><span>info</span><span>(</span><span>"Ending cleanly"</span><span>)</span>for message_id in list_of_message_ids: processed = False notifier, data = handle_each_email(service, message_id, logger) # Notifier tells us how the data dict is structured if notifier == "BUS": logger.debug("Bus") # Your notification / bot code here processed = True elif notifier == "KIDZDUO": logger.debug("KidsDuo") # Your notification / bot code here processed = True elif notifier is None and data is not None: logger.warning(f"Subject matched but From was not matched") elif notifier is None and data is None: logger.info(f"Non-Notification email from expected sender") else: # We should not get here. But log it. logger.warning( f"Something went wrong. Unexpected match." f"Don't know how to handle data." ) if processed: # Mail was processed. Add label so it's not processed again # Gmail only allows for the shortest time interval of one day. logger.debug("adding label") add_label_to_message(service, message_id, secrets.LABEL_ID) # End of the program logger.info("Ending cleanly")
Enter fullscreen mode Exit fullscreen mode
Note that there are several checks that should probably be made. The return of add_label_to_message()
for example.
I will be making the code available in GitHub later. Along with the systemd configuration I used to have the script run at select times of the day and week. Watch for the future post on this.
Google API Gmail LINE Notifications (7 Part Series)
1 Connecting to Gmail API with Python
2 Managing your Labels in Gmail
… 3 more parts…
3 Searching for and Getting Emails
4 Processing Email Contents
5 Processing Email – Being Selective
6 Processing Email – Single Script / Multiple Emails
7 Automation with Systemd & Code
暂无评论内容