Send to Kindle via email – using browser automation

I often read web articles on the Kindle e-reader for less eye strain. In order to achieve that, I use the Send to Kindle browser extension, maintained by Amazon.

In the past few years many of Kindle publishing services just came and go, so the official Send to Kindle was always the most stable one to use.

However, one important feature is not supported, sending articles from mobile or tablet – by just sending the URL to an email address, not an article’s content attached, processable by the @free.kindle.com email addresses.

Here, my script is presented, which could run on any machines using Python to check an email box with unread emails containing URLs and send the articles’ content to the Kindle e-reader.

Approaches

  • I’ve experimented with a few, supposedly more elegant approaches
    • Web page content parsing
      • Readability libraries are now maintained by Mercury as an API at https://mercury.postlight.com/web-parser/ – it was not capable of handling special characters (ISO-8859-2 charset, with e.g. accents as ????)
      • Used the Python-readability project, that errored out on some webpages

So I decided to leverage the most stable parser for the purpose, the Amazon extension that I use day by day on a manual basis.

Send to Kindle via web browser automation – Specification

  • Read a Gmail account’s specific folder for unread emails
    • In the below script, Kindle IMAP label, created in Gmail
  • Parse the first http(s) URL in the email
  • Open up Chrome browser with the send to Kindle extension
  • Use the Send to Kindle extension to send the article content to the Kindle
  • Mark the messages processed as read

Prerequisites

  • Windows or Linux
  • Python 2.7
  • Chrome browser (Firefox was not working with Selenium and Send-To-Kindle extension
  • Selenium’s Chrome web driver installed
  • pip install selenium
  • Gmail rule setup, emails sent to mail+kindle@gmail.com should be labeled as Kindle (= put to Kindle IMAP folder)
    • Email messages sent to your_mail+[anytoken]@gmail.com will arrive to your inbox, but you can setup various filtering rules for managing such emails

Script

Richly commented Python 2.7 code, reading and processing emails

gmail_uname = '' #without @gmail.com
gmail_password = ''
imap_folder_of_mails = 'Kindle'
amz_uname = ''
amz_password = ''
chromepath = 'C:/Users/{user}/AppData/Local/Google/Chrome/User Data' #use / and don't add /Default at the end

import imaplib # For email parsing
import time # For waits
import sys
import re
from selenium import webdriver #For selenium
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options


def read():
    # Login to Kindle Imap folder of gmail
    imap = imaplib.IMAP4_SSL("imap.gmail.com", 993)
    imap.login(gmail_uname, gmail_password)
    imap.select(imap_folder_of_mails)

    # Use search(), not status() to get unread messages
    status, response = imap.search(None, '(UNSEEN)')
    unread_msg_nums = response[0].split()
    
    # Get the content of the unread messages
    for e_id in unread_msg_nums:
        _, response = imap.fetch(e_id, '(UID BODY[TEXT])')
        emails.append(response[0][1])  
	
    # Mark emails as read - not necessary as parsed emails will be marked as read without this command
    # for e_id in unread_msg_nums:
    #    imap.store(e_id, '+FLAGS', 'Seen')

# Tuple hosting all the pages to be sent
emails = [] 
# Parse the emails for the URLs
read()
if emails:
    # Open the browser
    options = webdriver.ChromeOptions()
    options.add_argument('user-data-dir=' + chromepath) #Path to your chrome profile
    driver = webdriver.Chrome(chrome_options=options)
    actions = ActionChains(driver)

# For each of the websites
    for email in emails:
        try:
            # Get the first word of the email, that is the URL to be sent to Kindle
            words = re.findall(r'(https?://S+)', email)    
            url = words[0]
            print url
            driver.get(url)
    	# Wait 11 seconds - needed for Send to Kindle extension
            time.sleep(11)
            # Push SHIFT + K for send to Kindle extension to send the article
            actions.key_down(Keys.ALT).send_keys('k').key_up(Keys.ALT).perform()
          	# Wait for the article to be sent
            time.sleep(13)
       	# Login to Amazon, if needed
            try:
                # Check for Amazon logo - not a necessary step - if not found, jump to exception
                driver.find_element_by_xpath("//html/body/div[1]/div[1]/div[3]/div/div/form/div/div/div/h1") 
                # Send the UserID
                element = driver.find_element_by_xpath("//html/body/div[1]/div[1]/div[3]/div/div/form/div/div/div/div[1]/input")
                element.clear()
                element.send_keys(amz_uname)
                # Send the Amazon Password
                element = driver.find_element_by_xpath("//html/body/div[1]/div[1]/div[3]/div/div/form/div/div/div/div[2]/input")
                element.clear()
                element.send_keys(amz_password)
                # Click the login button
                element = driver.find_element_by_xpath("//html/body/div[1]/div[1]/div[3]/div/div/form/div/div/div/div[3]/span/span/input")
                element.click() 
                # Wait for parsing by Send to Kindle extension
                time.sleep(20)
            except:
                print "Already logged in / changed login screen", sys.exc_info()[0]        
        except:
            print "Unparseable URL in email", sys.exc_info()[0]
    driver.quit()