Skip to content

Commit 982a67d

Browse files
committed
Update email tutorial to read a mailbox successfully
1 parent 2fcef6d commit 982a67d

File tree

1 file changed

+84
-51
lines changed

1 file changed

+84
-51
lines changed

TUTORIALS.md

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,8 @@ To add the necessary resource definitions, add them to the `domain.lisp` file as
573573
```lisp
574574
(define-resource mail ()
575575
:class (s-prefix "example:Mail")
576-
:properties `((:sender :string ,(s-prefix "example:sender"))
576+
:properties `((:from :string ,(s-prefix "example:from"))
577+
(:to :string ,(s-prefix "example:to"))
577578
(:subject :string ,(s-prefix "example:subject"))
578579
(:content :string ,(s-prefix "example:content"))
579580
(:ready :string ,(s-prefix "example:ready")))
@@ -628,7 +629,8 @@ Body:
628629
{
629630
"data":{
630631
"attributes":{
631-
"sender":"[email protected]",
632+
633+
632634
"subject":"Mu Semtech Mail Server",
633635
"content":"This is a test for the Mu Semtech Mail Server.",
634636
"ready":"no"
@@ -643,7 +645,8 @@ This gives us the following reponse:
643645
{
644646
"data": {
645647
"attributes": {
646-
"sender": "[email protected]",
648+
"from": "[email protected]",
649+
647650
"subject": "Mu Semtech Mail Server",
648651
"content": "This is a test for the Mu Semtech Mail Server.",
649652
"ready": "no"
@@ -662,7 +665,8 @@ To verify the original get request again, this now produces:
662665
{
663666
"data": {
664667
"attributes": {
665-
"sender": "[email protected]",
668+
"from": "[email protected]",
669+
666670
"subject": "Mu Semtech Mail Server",
667671
"content": "This is a test for the Mu Semtech Mail Server.",
668672
"ready": "no"
@@ -725,11 +729,11 @@ and then create `config/delta-service/subscribers.json` and put this JSON inside
725729
If we run `docker-compose rm` and then `docker-compose up` again, the delta service will be booting and already monitoring the changes that happen in the database! Of course we are not doing anything with them yet. So we will create a new micro-service just for this purpose.
726730

727731
#### The mail-fetching microservice
728-
The next step is to build our mail handling microservice. To do this we create a new directory called `mail-service` in our base directory. Then we create a file in that directory called `Dockerfile`. We will start from a mu.semte.ch template to make developing this microservice that much quicker. Mu.semte.ch has templates for a bunch of languages ruby, javascript, python, … For this microservice we will go for python 2.7. To do this we simply need to create a `web.py` file which will serve as the location for our code. Next add the following to the Dockerfile:
732+
The next step is to build our mail handling microservice. To do this we create a new directory called `mail-service` in our base directory. Then we create a file in that directory called `Dockerfile`. We will start from a mu.semte.ch template to make developing this microservice that much quicker. Mu.semte.ch has templates for a bunch of languages ruby, javascript, python, … For this microservice we will go for python 3. To do this we simply need to create a dockerfile to build the container and a `web.py` file which will serve as the location for our code. First we create the file 'Dockerfile' in our mail-service directory:
729733

730734
```dockerfile
731735
# mail-service/Dockerfile
732-
FROM semtech/mu-python-template
736+
FROM semtech/mu-python-template:2.0.0-beta.2
733737
734738
MAINTAINER Langens Jonathan <[email protected]>
735739
```
@@ -739,74 +743,103 @@ I know it doesn’t say much, but it doesn’t need to. The python template will
739743
Then we need to add some mail manipulating functionality. Since this is not really the objective of this post I create a `mail_helpers.py` file and paste the following code in there:
740744
```python
741745
# mail-service/mail_helpers.py
742-
import sys
743-
import imaplib
744-
import getpass
745746
import email
746-
import datetime
747747
import uuid
748748
import helpers
749-
750-
def save_mail(sender, date, subject, content):
751-
str_uuid = str(uuid.uuid4())
752-
insert_query = "INSERT DATA\n{\nGRAPH <http://mu.semte.ch/application>\n{\n<http://mail.com/examples/mail/" + str_uuid + "> a <http://mail.com/Mail>;\n"
753-
insert_query += "<http://mail.com/from> \"" + sender + "\";\n"
754-
insert_query += "<http://mail.com/date> \"" + date + "\";\n"
755-
insert_query += "<http://mail.com/content> \"" + content + "\";\n"
756-
insert_query += "<http://mail.com/subject> \"" + subject + "\";\n"
757-
insert_query += "<http://mu.semte.ch/vocabularies/core/uuid> \"" + str_uuid + "\".\n"
758-
insert_query += "}\n}"
759-
print "query:\n", insert_query
760-
helpers.update(insert_query)
749+
from escape_helpers import sparql_escape_string
750+
751+
def save_mail(sender, subject, content):
752+
str_uuid = str(uuid.uuid4())
753+
insert_query = (
754+
f'INSERT DATA\n{{\nGRAPH <http://mu.semte.ch/application>\n{{'
755+
f' <http://example.com/examples/mail/{str_uuid}> a <http://example.com/Mail>;\n'
756+
f' <http://example.com/from> {sparql_escape_string(sender)};\n'
757+
f' <http://example.com/content> {sparql_escape_string(content)};\n'
758+
f' <http://example.com/subject> {sparql_escape_string(subject)};\n'
759+
f' <http://mu.semte.ch/vocabularies/core/uuid> "{str_uuid}".\n'
760+
f'}}\n}}'
761+
)
762+
helpers.log(f"query:\n{insert_query}")
763+
helpers.update(insert_query)
761764
762765
def process_mailbox(mailbox):
763-
rv, data = mailbox.search(None, "ALL")
764-
if rv != 'OK':
765-
print "No messages found!"
766-
return
766+
rv, data = mailbox.search(None, "ALL")
767+
if rv != 'OK' or not data[0]:
768+
helpers.log("No messages found!")
769+
return
770+
else:
771+
helpers.log("You've got mail!")
772+
773+
for num in data[0].split():
774+
rv, data = mailbox.fetch(num, '(RFC822)')
775+
if rv != 'OK':
776+
helpers.log("ERROR getting message", num)
777+
return
767778
768-
for num in data[0].split():
769-
rv, data = mailbox.fetch(num, '(RFC822)')
770-
if rv != 'OK':
771-
print "ERROR getting message", num
772-
return
773-
774-
msg = email.message_from_string(data[0][1])
775-
content = str(msg.get_payload())
776-
content = content.replace('\n','')
779+
msg = email.message_from_string(data[0][1].decode())
780+
content = str(msg.get_payload())
777781
778-
save_mail(msg['From'], msg['Date'], msg['Subject'], content)
782+
save_mail(msg['From'], msg['Date'], msg['Subject'], content)
779783
```
780784

781785
As you can see the mail_helpers contain 2 functions, one to iterate over all emails in a mailbox and the other to save a single email to the triple store. Easy peasy!
782786

783-
Next we create `web.py`. For more information on how the python template can be used you can visit: https://github.com/mu-semtech/mu-python-template. I created the following method to process all mails:
787+
Next we create `web.py`. For more information on how the python template can be used you can visit: https://github.com/mu-semtech/mu-python-template. We create the following method to add a GET route to process all mails:
784788
```python
785789
# mail-service/web.py
790+
from os import environ
791+
from imaplib import IMAP4, IMAP4_SSL
792+
793+
import mail_helpers
794+
795+
EMAIL_ADDRESS = environ['EMAIL_ADDRESS']
796+
EMAIL_PWD = environ['EMAIL_PWD']
797+
786798
@app.route("/fetchMails")
787799
def fetchMailMethod():
788-
EMAIL_ADDRESS = "address"
789-
EMAIL_PWD = "pwd"
790-
791-
MAIL_SERVER = imaplib.IMAP4_SSL('imap.gmail.com')
800+
MAIL_SERVER = IMAP4_SSL(environ['IMAP_SERVER'])
792801
793-
try:
794-
MAIL_SERVER.login(EMAIL_ADDRESS, EMAIL_PWD)
795-
except imaplib.IMAP4.error:
796-
print "Logging into mailbox failed! "
802+
try:
803+
MAIL_SERVER.login(EMAIL_ADDRESS, EMAIL_PWD)
804+
except IMAP4.error:
805+
return "Unable to log in to IMAP server", 503
797806
798-
rv, data = MAIL_SERVER.select("INBOX")
799-
if rv == 'OK':
800-
mail_helpers.process_mailbox(MAIL_SERVER)
801-
MAIL_SERVER.close()
807+
rv, data = MAIL_SERVER.select("INBOX")
808+
if rv == 'OK':
809+
mail_helpers.process_mailbox(MAIL_SERVER)
810+
MAIL_SERVER.close()
802811
803-
MAIL_SERVER.logout()
812+
MAIL_SERVER.logout()
804813
805-
return "ok"
814+
return "ok"
806815
```
807816

808817
This method is rather straightforward: it just opens a connection to an email address and opens the inbox mailbox. It then selects it for processing, thus inserting all mails into the triple store.
809818

819+
The last step to create this service is to add it to our docker-compose.yml file:
820+
821+
```yaml
822+
mailservice:
823+
build: ./mail-service
824+
links:
825+
- database:database
826+
ports:
827+
# Forward a port for testing purposes
828+
- "8888:80"
829+
environment:
830+
# Set the python template to development mode to enable auto reloading
831+
MODE: "development"
832+
LOG_LEVEL: "debug"
833+
# This email should work but won't send any real mails, check ethereal.email for details
834+
# You can replace this with a real email SMTP server but beware, this will send real mails!
835+
EMAIL_ADDRESS: "[email protected]"
836+
EMAIL_PWD: "zwaFZ3P5RDnDcWwRhA"
837+
IMAP_SERVER: "imap.ethereal.email"
838+
volumes:
839+
# Bind our repo to the container, so we can edit files without having to rebuild the container
840+
- ./mail-service:/app
841+
```
842+
810843
At this point, we have:
811844
- Defined a JSONAPI through which we can access our emails, using the standard mu.semte.ch stack
812845
- Built a custom service which fetches the emails from our mail account and inserts them into the triplestore using the right model

0 commit comments

Comments
 (0)