Merge branch 'better-smtp-developer-user-story' into 'master'
remove SMTP server from dev and use console logs instead. See merge request engineering/make-post-sell/make_post_sell!37
This commit is contained in:
commit
ac49dc8ed4
5 changed files with 119 additions and 56 deletions
|
|
@ -43,5 +43,10 @@ build:
|
|||
deploy:
|
||||
stage: deploy
|
||||
needs: [build]
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "develop"'
|
||||
when: on_success
|
||||
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||
when: on_success
|
||||
trigger:
|
||||
include: .gitlab-ci.deploy.yml
|
||||
|
|
|
|||
5
Makefile
5
Makefile
|
|
@ -29,8 +29,3 @@ test: dev
|
|||
serve: dev
|
||||
# In the first shell, run a copy of make_post_sell using:
|
||||
env/bin/pserve development.ini --reload
|
||||
|
||||
smtp: env
|
||||
# In the third shell, run a "mock" SMTP server to catch log in emails:
|
||||
#sudo env/bin/python -m smtpd -n -c DebuggingServer localhost:25
|
||||
sudo env/bin/python -m aiosmtpd -l 127.0.0.1:8025
|
||||
|
|
|
|||
39
README.rst
39
README.rst
|
|
@ -28,12 +28,6 @@ Run a web application server with the project loaded:
|
|||
|
||||
make serve
|
||||
|
||||
Open a 2nd shell and run a test SMTP server for intercepting OTP login uri's:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make smtp
|
||||
|
||||
In a web browser navagate to http://127.0.0.1:6501/ and you should see the app.
|
||||
|
||||
|
||||
|
|
@ -128,3 +122,36 @@ For example, we needed to migrate production data using this script:
|
|||
|
||||
# commit/close the database transaction to really make changes.
|
||||
request.tm.commit()
|
||||
|
||||
|
||||
Contributing
|
||||
===================
|
||||
|
||||
* Establish communication with Russell or another admin to bless your git.unturf.com gitlab account & put you into the proper roles.
|
||||
* Russell should see your account request but due to spam you have to ask him directly for approval via email or some other means of comms.
|
||||
* Clone repo & make commits
|
||||
* Create merge requests, we automatically run the unit & headless functional tests on each commit
|
||||
* On merge we release to the production site & see the change across users.
|
||||
|
||||
Optionally, format your code.
|
||||
|
||||
This is not set in stone, but if you want to use a formatter this is the path for now!
|
||||
|
||||
**Python**
|
||||
black (manual)
|
||||
|
||||
**Jinja2**
|
||||
None (not needed, neither is an HTML formatter)
|
||||
|
||||
**JavaScript**
|
||||
Prettier or biome (manual)
|
||||
|
||||
**CSS**
|
||||
Prettier or biome (manual)
|
||||
|
||||
|
||||
Licence
|
||||
=====================
|
||||
|
||||
All code contributed goes into the public domain.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ except ImportError:
|
|||
# Python 3.
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
|
||||
from make_post_sell.lib.mail_messages import (
|
||||
WELCOME_1_TEXT,
|
||||
WELCOME_1_HTML,
|
||||
|
|
@ -19,17 +18,15 @@ from make_post_sell.lib.mail_messages import (
|
|||
INVITE_1_HTML,
|
||||
)
|
||||
|
||||
|
||||
import dkim
|
||||
|
||||
import smtplib
|
||||
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
# catch socket errors when postfix isn't running...
|
||||
# Catch socket errors when postfix isn't running...
|
||||
from socket import error as socket_error
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def send_email(
|
||||
|
|
@ -42,17 +39,18 @@ def send_email(
|
|||
dkim_private_key_path="",
|
||||
dkim_selector="",
|
||||
dkim_signature_algorithm="ed25519-sha256",
|
||||
debug_mode=False,
|
||||
):
|
||||
# the `email` library assumes it is working with string objects.
|
||||
# the `dkim` library assumes it is working with byte objects.
|
||||
# this function performs the acrobatics to make them both happy.
|
||||
# The `email` library assumes it is working with string objects.
|
||||
# The `dkim` library assumes it is working with byte objects.
|
||||
# This function performs the acrobatics to make them both happy.
|
||||
|
||||
if isinstance(message_text, bytes):
|
||||
# needed for Python 3.
|
||||
# Needed for Python 3.
|
||||
message_text = message_text.decode()
|
||||
|
||||
if isinstance(message_html, bytes):
|
||||
# needed for Python 3.
|
||||
# Needed for Python 3.
|
||||
message_html = message_html.decode()
|
||||
|
||||
sender_domain = sender_email.split("@")[-1]
|
||||
|
|
@ -71,40 +69,71 @@ def send_email(
|
|||
msg_data = msg.as_string()
|
||||
|
||||
if dkim_private_key_path and dkim_selector:
|
||||
# the dkim library uses regex on byte strings so everything
|
||||
# needs to be encoded from strings to bytes.
|
||||
with open(dkim_private_key_path) as fh:
|
||||
dkim_private_key = fh.read()
|
||||
headers = [b"To", b"From", b"Subject"]
|
||||
sig = dkim.sign(
|
||||
message=msg_data,
|
||||
selector=str(dkim_selector).encode(),
|
||||
domain=sender_domain.encode(),
|
||||
privkey=dkim_private_key.encode(),
|
||||
include_headers=headers,
|
||||
signature_algorithm=dkim_signature_algorithm.encode(),
|
||||
)
|
||||
# add the dkim signature to the email message headers.
|
||||
# decode the signature back to string_type because later on
|
||||
# the call to msg.as_string() performs it's own bytes encoding...
|
||||
msg["DKIM-Signature"] = sig[len("DKIM-Signature: ") :].decode()
|
||||
|
||||
try:
|
||||
# Python 3 libraries expect bytes.
|
||||
msg_data = msg.as_bytes()
|
||||
except:
|
||||
# Python 2 libraries expect strings.
|
||||
msg_data = msg.as_string()
|
||||
# The dkim library uses regex on byte strings so everything
|
||||
# needs to be encoded from strings to bytes.
|
||||
with open(dkim_private_key_path) as fh:
|
||||
dkim_private_key = fh.read()
|
||||
headers = [b"To", b"From", b"Subject"]
|
||||
sig = dkim.sign(
|
||||
message=msg_data,
|
||||
selector=str(dkim_selector).encode(),
|
||||
domain=sender_domain.encode(),
|
||||
privkey=dkim_private_key.encode(),
|
||||
include_headers=headers,
|
||||
signature_algorithm=dkim_signature_algorithm.encode(),
|
||||
)
|
||||
# Add the dkim signature to the email message headers.
|
||||
# Decode the signature back to string_type because later on
|
||||
# the call to msg.as_string() performs its own bytes encoding...
|
||||
msg["DKIM-Signature"] = sig[len("DKIM-Signature: ") :].decode()
|
||||
|
||||
# TODO: react if connecting to relay (localhost postfix) is a socket error.
|
||||
s = smtplib.SMTP(relay)
|
||||
s.sendmail(sender_email, [to_email], msg_data)
|
||||
s.quit()
|
||||
return msg
|
||||
try:
|
||||
# Python 3 libraries expect bytes.
|
||||
msg_data = msg.as_bytes()
|
||||
except AttributeError: # For Python 2 compatibility
|
||||
# Python 2 libraries expect strings.
|
||||
msg_data = msg.as_string()
|
||||
except Exception as e:
|
||||
if debug_mode:
|
||||
log.error(f"DKIM signing failed: {str(e)}")
|
||||
raise
|
||||
|
||||
try:
|
||||
s = smtplib.SMTP(relay)
|
||||
s.sendmail(sender_email, [to_email], msg_data)
|
||||
s.quit()
|
||||
return msg
|
||||
except (socket_error, smtplib.SMTPException) as e:
|
||||
error_msg = f"Failed to send email: {str(e)}"
|
||||
|
||||
if debug_mode:
|
||||
# Log the error first for quick scanning
|
||||
log.error(error_msg)
|
||||
# Then log the email details
|
||||
log.info(
|
||||
f"""
|
||||
|
||||
Email Contents:
|
||||
To: {to_email}
|
||||
From: {sender_email}
|
||||
Subject: {subject}
|
||||
|
||||
Text Content:
|
||||
{message_text}
|
||||
|
||||
HTML Content:
|
||||
{message_html}
|
||||
"""
|
||||
)
|
||||
|
||||
if not debug_mode:
|
||||
raise
|
||||
return None
|
||||
|
||||
|
||||
def send_pyramid_email(request, to_email, subject, message_text, message_html):
|
||||
"""Thin wrapper around `send_email` to customise settings using request object."""
|
||||
"""Thin wrapper around `send_email` to customize settings using request object."""
|
||||
default_sender = f"no-reply@{request.domain}"
|
||||
sender_email = request.app.get("email.sender", default_sender)
|
||||
subject = f"{subject} | {request.app.get('email.subject_postfix', request.domain)}"
|
||||
|
|
@ -125,6 +154,7 @@ def send_pyramid_email(request, to_email, subject, message_text, message_html):
|
|||
dkim_private_key_path,
|
||||
dkim_selector,
|
||||
dkim_signature_algorithm,
|
||||
request.debug_mode,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -225,17 +255,17 @@ def send_sale_email(request, shop, products, total_cost):
|
|||
subject, shop.absolute_sales_url(request), product_text, f"{total_cost:.2f}"
|
||||
)
|
||||
|
||||
# send a separate email for each shop owner.
|
||||
# Send a separate email for each shop owner.
|
||||
for user in shop.users:
|
||||
send_pyramid_email(request, user.email, subject, message_text, message_html)
|
||||
|
||||
|
||||
def send_invite_email(request, to_email, user, shop):
|
||||
"""
|
||||
Send an email to all shop owners regarding the sale.
|
||||
Send an email to invite a user to join a shop.
|
||||
|
||||
request
|
||||
the request (of the successful log in attempt)
|
||||
the request (of the invitation)
|
||||
|
||||
to_email
|
||||
the email address to send the shop invitation to.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ def includeme(config):
|
|||
# get the app_settings from the config file.
|
||||
app_settings = get_children_settings(config.get_settings(), "app")
|
||||
|
||||
def add_debug_mode(request):
|
||||
"""Return True if debug toolbar is enabled."""
|
||||
return "pyramid_debugtoolbar" in request.registry.settings.get(
|
||||
"pyramid.includes", ""
|
||||
)
|
||||
|
||||
def add_user(request):
|
||||
"""Return User object or None. User.authenticated may be True or False."""
|
||||
user = None
|
||||
|
|
@ -164,7 +170,7 @@ def includeme(config):
|
|||
|
||||
# Register functions to app config as request methods.
|
||||
# To prevent multiple DB lookups, cache result with `reify=True`.
|
||||
|
||||
config.add_request_method(add_debug_mode, "debug_mode", reify=True)
|
||||
config.add_request_method(add_user, "user", reify=True)
|
||||
config.add_request_method(add_active_cart, "active_cart", reify=True)
|
||||
config.add_request_method(add_session_cart, "session_cart", reify=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue