Skip to content

Commit 58a5ebd

Browse files
committed
Add small tools for testing email sending and parsing
These tools can be used for any app that uses the "pgweb style mailqueue" module, including for example pgeu-system.
1 parent fc120c1 commit 58a5ebd

5 files changed

Lines changed: 153 additions & 0 deletions

File tree

tools/email/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config.yaml

tools/email/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# email tools
2+
3+
This directory holds a few trivial email testing tools. They work on emails
4+
that are in the `mailqueue` app, so they first have to be generated (with pgweb
5+
that's typically done by approving news or using the `news_send_email` command),
6+
and then referenced by their id number. They are used to test formats and markups.
7+
8+
## parse_email.py
9+
10+
This tool will simply parse and print the MIME structure of the email in question.
11+
12+
## send_email.py
13+
14+
This tools will take the email and send it out using SMTP/AUTH (hardcoded to always
15+
have STARTTLS) according to the settings in `config.yaml` for end-to-end testing.
16+
17+
Note that emails are *not* removed from the queue when sent this way! This way they
18+
can be sent to multiple addresses for testing.
19+
20+
## config.yaml
21+
22+
Used for both tools to find their database, and for `send_email.py` to know how to
23+
connect to the server. See the `config.yaml.sample` file for example/docs.

tools/email/config.yaml.sample

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
mail:
3+
server: smtp.example.com # your server
4+
port: 587
5+
user: someone@example.com # to log in with
6+
sender: someone@example.com # Email to use as envelope sender. Usually, but not always, matches user.
7+
password: # Can be either a string here
8+
smtp: someone@example.com # Or a lookup of a key/value pair in the secrets store
9+
db: # List of databases that we can connect to
10+
pgweb: dbname=pgweb # Each in psyocpg2 dsn format
11+
pgeu: dbname=postgresqleu

tools/email/parse_email.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import email
5+
import email.policy
6+
import psycopg2
7+
import sys
8+
import yaml
9+
10+
11+
def print_message(msg, level=0):
12+
def _out(t):
13+
print("{}{}".format(' ' * level, t))
14+
15+
_out(msg.get_content_type())
16+
if msg.is_multipart():
17+
for p in msg.iter_parts():
18+
print_message(p, level + 1)
19+
20+
21+
if __name__ == "__main__":
22+
parser = argparse.ArgumentParser("Print email MIME structure")
23+
parser.add_argument('db', help='Name of database (looked up in config.yaml) t connect to')
24+
parser.add_argument('id', type=int, help='ID of email entry to send')
25+
26+
args = parser.parse_args()
27+
28+
with open('config.yaml') as f:
29+
config = yaml.load(f, Loader=yaml.SafeLoader)
30+
31+
if args.db not in config['db']:
32+
print("Non-existing db specified")
33+
sys.exit(1)
34+
35+
# Connect to db and get message
36+
dbconn = psycopg2.connect(config['db'][args.db])
37+
curs = dbconn.cursor()
38+
curs.execute("SELECT fullmsg FROM mailqueue_queuedmail WHERE id=%(id)s", {
39+
'id': args.id,
40+
})
41+
r = curs.fetchall()
42+
dbconn.close()
43+
44+
if len(r) == 0:
45+
print("Email not found")
46+
sys.exit(1)
47+
48+
msg = email.message_from_string(r[0][0], policy=email.policy.default)
49+
50+
print_message(msg)

tools/email/send_email.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import psycopg2
5+
import smtplib
6+
import sys
7+
import yaml
8+
9+
10+
if __name__ == "__main__":
11+
parser = argparse.ArgumentParser("Email tester")
12+
parser.add_argument('db', help='Name of database (looked up in config.yaml) t connect to')
13+
parser.add_argument('id', type=int, help='ID of email entry to send')
14+
parser.add_argument('recipient', help='Email address of recipient to send to')
15+
16+
args = parser.parse_args()
17+
18+
with open('config.yaml') as f:
19+
config = yaml.load(f, Loader=yaml.SafeLoader)
20+
21+
if args.db not in config['db']:
22+
print("Non-existing db specified")
23+
sys.exit(1)
24+
25+
if isinstance(config['mail']['password'], str):
26+
password = config['mail']['password']
27+
elif isinstance(config['mail']['password'], dict):
28+
import secretstorage
29+
coll = secretstorage.get_default_collection(secretstorage.dbus_init())
30+
if coll.is_locked():
31+
coll.unlock()
32+
r = list(coll.search_items(config['mail']['password']))
33+
if len(r) == 0:
34+
print("Could not find password in secret storage.")
35+
sys.exit(1)
36+
elif len(r) > 1:
37+
print("Found more than one password, try again.")
38+
sys.exit(1)
39+
password = r[0].get_secret().decode()
40+
else:
41+
print("Invalid type for password in configuration")
42+
sys.exit(1)
43+
44+
# Connect to db and get message
45+
dbconn = psycopg2.connect(config['db'][args.db])
46+
curs = dbconn.cursor()
47+
curs.execute("SELECT fullmsg FROM mailqueue_queuedmail WHERE id=%(id)s", {
48+
'id': args.id,
49+
})
50+
r = curs.fetchall()
51+
dbconn.close()
52+
53+
if len(r) == 0:
54+
print("Email not found")
55+
sys.exit(1)
56+
57+
msg = r[0][0]
58+
59+
# Now do it!
60+
smtp = smtplib.SMTP(host=config['mail']['server'], port=config['mail']['port'])
61+
smtp.starttls()
62+
smtp.login(user=config['mail']['user'], password=password)
63+
64+
smtp.sendmail(config['mail']['sender'], args.recipient, msg)
65+
66+
smtp.quit()
67+
68+
print("Sent.")

0 commit comments

Comments
 (0)