content/blog/2022-06-22-daily-poetry.org

0b2895a90c7751ce06e4b26d4db41005ea2f40a9
cleberg.net/content/blog/2022-06-22-daily-poetry.org rendered · source · history · blame · raw

219 lines · 7451 bytes

  1#+date:        <2022-06-22 Wed 00:00:00>
  2#+title:       Automated Retrieval and Delivery of Daily Plaintext Poetry via Email
  3#+description: Instructional guide for scheduling and automating the distribution of classic and contemporary poetry in plaintext format to an electronic mail inbox using Python scripting and SMTP protocol.
  4#+slug:        daily-poetry
  5#+filetags:    :poetry:email:automation:
  6
  7* Source Code
  8
  9I don't want to bury the lede here, so if you'd like to see the full source code
 10I use to email myself plaintext poems daily, visit the repository:
 11[[https://github.com/ccleberg/daily-poem][daily-poem.git]].
 12
 13* My Daily Dose of Poetry
 14
 15Most of my programming projects are small, random projects that are made
 16strictly to fix some small problem I have or enhance my quality of life.
 17
 18In this case, I was looking for a simply and easy way to get a daily
 19dose of literature or poetry to read in the mornings.
 20
 21However, I don't want to sign up for a random mailing list on just any
 22website. I also don't want to have to work to find the reading content
 23each morning, as I know I would simply give up and stop reading daily.
 24
 25Thus, I found a way to deliver poetry to myself in plain-text format, on
 26a daily basis, and scheduled to deliver automatically.
 27
 28* Prerequisites
 29
 30This solution uses Python and email, so the following process requires
 31the following to be installed:
 32
 331. An SMTP server, which can be as easy as installing =mailutils= if
 34   you're on a Debian-based distro.
 352. Python (& pip!)
 363. The following Python packages: =email=, =smtplib=, =json=, and
 37   =requests=
 38
 39* Breaking Down the Logic
 40
 41I want to break down the logic for this program, as it's quite simple
 42and informational.
 43
 44** Required Packages
 45
 46This program starts with a simple import of the required packages, so I
 47wanted to explain why each package is used:
 48
 49#+begin_src python
 50from email.mime.text import MIMEText # Required for translating MIMEText
 51import smtplib # Required to process the SMTP mail delivery
 52import json # Required to parse the poetry API results
 53import requests # Required to send out a request to the API
 54#+end_src
 55
 56** Sending the API Request
 57
 58Next, we need to actually send the API request. In my case, I'm calling
 59a random poem from the entire API. If you want, you can call specific
 60poems or authors from this API.
 61
 62#+begin_src python
 63json_data = requests.get('https://poetrydb.org/random').json()
 64#+end_src
 65
 66This gives us the following result in JSON:
 67
 68#+begin_src json
 69[
 70    {
 71        "title": "Sonnet XXII: With Fools and Children",
 72        "author": "Michael Drayton",
 73        "lines": [
 74            "To Folly",
 75            "",
 76            "With fools and children, good discretion bears;",
 77            "Then, honest people, bear with Love and me,",
 78            "Nor older yet, nor wiser made by years,",
 79            "Amongst the rest of fools and children be;",
 80            "Love, still a baby, plays with gauds and toys,",
 81            "And, like a wanton, sports with every feather,",
 82            "And idiots still are running after boys,",
 83            "Then fools and children fitt'st to go together.",
 84            "He still as young as when he first was born,",
 85            "No wiser I than when as young as he;",
 86            "You that behold us, laugh us not to scorn;",
 87            "Give Nature thanks you are not such as we.",
 88            "Yet fools and children sometimes tell in play",
 89            "Some, wise in show, more fools indeed than they."
 90        ],
 91        "linecount": "15"
 92    }
 93]
 94#+end_src
 95
 96** Parsing the API Results
 97
 98In order to parse this into a readable format, we need to use the =json=
 99package and extract the fields we want. In the example below, I am
100grabbing every field presented by the API.
101
102For the actual poem content, we need to loop over each line in the
103=lines= variable since each line is a separate string by default.
104
105#+begin_quote
106You /could/ also extract the title or author and make another call out
107to the API to avoid having to build the plaintext poem with a loop, but
108it just doesn't make sense to me to send multiple requests when we can
109create a simple loop on our local machine to work with the data we
110already have.
111
112For
113[[https://poetrydb.org/title/Sonnet%20XXII:%20With%20Fools%20and%20Children/lines.text][example]],
114look at the raw data response of this link to see the poem's lines
115returned in plaintext.
116#+end_quote
117
118#+begin_src python
119title = json_data[0]['title']
120author = json_data[0]['author']
121line_count = json_data[0]['linecount']
122lines = ''
123for line in json_data[0]['lines']:
124  lines = lines + line + "\n"
125#+end_src
126
127** Composing the Email
128
129Now that I have all the data I need, I just need to compose it into a
130message and prepare the message metadata.
131
132For my daily email, I want to see the title of the poem first, followed
133by the author, then a blank line, and finally the full poem. This code
134snippet combines that data and packages it into a MIMEText container,
135ready to be emailed.
136
137#+begin_src python
138msg_body = title + "\n" + author + "\n\n" + lines
139msg = MIMEText(msg_body)
140#+end_src
141
142Before we send the email, we need to prepare the metadata (subject,
143from, to, etc.):
144
145#+begin_src python
146sender_email = 'example@server.local'
147recipient_emails = ['user@example.com']
148msg['Subject'] = 'Your Daily Poem (' + line_count + ' lines)'
149msg['From'] = sender_email
150msg['To'] = recipient_email
151#+end_src
152
153** Sending the Email
154
155Now that I have everything ready to be emailed, the last step is to
156simply connect to an SMTP server and send the email out to the
157recipients. In my case, I installed =mailutils= on Ubuntu and let my
158SMTP server be =localhost=.
159
160#+begin_src python
161smtp_server = 'localhost'
162s = smtplib.SMTP(smtp_server)
163s.sendmail(sender_email, recipient_emails, msg.as_string())
164s.quit()
165#+end_src
166
167* The Result!
168
169Instead of including a screenshot, I've copied the contents of the email
170that was delivered to my inbox below since I set this process up in
171plaintext format.
172
173#+begin_src txt
174Date: Wed, 22 Jun 2022 14:37:19 +0000 (UTC)
175From: REDACTED
176To: REDACTED
177Subject: Your Daily Poem (36 lines)
178MIME-Version: 1.0
179Content-Transfer-Encoding: 8bit
180Content-Type: text/plain; charset=utf-8
181
182Sonnet XXII: With Fools and Children
183Michael Drayton
184
185With fools and children, good discretion bears;
186Then, honest people, bear with Love and me,
187Nor older yet, nor wiser made by years,
188Amongst the rest of fools and children be;
189Love, still a baby, plays with gauds and toys,
190And, like a wanton, sports with every feather,
191And idiots still are running after boys,
192Then fools and children fitt'st to go together.
193He still as young as when he first was born,
194No wiser I than when as young as he;
195You that behold us, laugh us not to scorn;
196Give Nature thanks you are not such as we.
197Yet fools and children sometimes tell in play
198Some, wise in show, more fools indeed than they.
199#+end_src
200
201* Scheduling the Daily Email
202
203Last, but not least, is scheduling this Python script with =crontab=. To
204schedule a script to run daily, you can add it to the =crontab= file. To
205do this, open =crontab= in editing mode:
206
207#+begin_src sh
208crontab -e
209#+end_src
210
211In the file, simply paste the following snippet at the bottom of the
212file and ensure that the file path is correctly pointing to wherever you
213saved your Python script:
214
215#+begin_src config
2160 8 ** ** ** python3 /home/<your_user>/dailypoem/main.py
217#+end_src
218
219We have now set up the script and scheduled it to run daily at 08:00!