Using the SMTP bridge server¶
This library ships with a simple SMTP server implementation based on aiosmtpd. This can be used to provide a "sidecar" container for applications which can only use SMTP to send email. The SMTP server is unauthenticated and so make sure to deploy it only in appropriate environments.
Important
In order to use the SMTP server you must have installed the library with the
smtp extra, e.g. using pip install ucam-user-notify[smtp].
Although you can set the From address to whatever you like when sending email
via SMTP, it must still match one of the allowed From addresses for the
service.
Running the SMTP bridge via docker¶
The User Notify client library ships with a pre-built docker image which implements a SMTP bridge.
Running the docker image on ARM64 or Apple Silicon machines
If you're running Docker Desktop on Mac, you'll need to enable the host
network driver first. You'll also need to add --platform
linux/amd64 to the docker command line flags to use the image built for
AMD64.
You can run the image locally, after running gcloud auth application-default
login, using the following command:
docker run --rm --net host \
-v ~/.config/gcloud/application_default_credentials.json:/app/credentials.json:ro \
-e GOOGLE_APPLICATION_CREDENTIALS=/app/credentials.json \
-e USER_NOTIFY_SERVICE_NAME=testing-development \
-e USER_NOTIFY_IMPERSONATE_SERVICE_ACCOUNT=service-testing@user-notify-devel-2ff1dc8d.iam.gserviceaccount.com \
-e USER_NOTIFY_ENVIRONMENT=development \
-e USER_NOTIFY_SMTP_PORT=8025 \
europe-west2-docker.pkg.dev/user-notify-meta-251d3b6f/public/ucam-user-notify/smtp-bridge:1.4.20
You can send a test email via this server using this client script:
#!/usr/bin/env python
"""
An example of sending email via the unauthenticated SMTP server.
"""
import smtplib
# Note that the From address must correspond to one which this service
# is allowed to send from.
client = smtplib.SMTP("127.0.0.1", 8025)
client.sendmail(
"testing@ses.devel.user-notify.gcp.uis.cam.ac.uk",
"success@simulator.amazonses.com",
"Subject: Test\n\nThis is a test email.",
)
client.quit()
# vim:tw=70
The following environment variables can be used to configure behaviour:
USER_NOTIFY_SERVICE_NAME(required) - The name of the service as registered with User Notify.USER_NOTIFY_SMTP_PORT(optional) - The port which the service will listen on. Default: 1025.USER_NOTIFY_IMPERSONATE_SERVICE_ACCOUNT(optional) - The email address identifier of a Google Service Account to impersonate when authenticating to User Notify.USER_NOTIFY_ROLE_SESSION_NAME(optional) - Session name to use when assuming the AWS Role used to send email.USER_NOTIFY_ENVIRONMENT(optional) - The variant of User Notify to use. Ordinarily you will not need to set this variable.
Important
By design, the server will only listen on the local loopback interface. This is because it is intended to be deployed as a "sidecar" container in Cloud Run and be accessible only to the main service container.
Running the SMTP bridge via the aiosmtpd command¶
If the smtp extra was installed then the aiosmtpd command-line program
should be available. This can be used to start a SMTP server. Pass the
registered User Notify service name on the command line.
USER_NOTIFY_ENVIRONMENT=development \
USER_NOTIFY_IMPERSONATE_SERVICE_ACCOUNT=service-testing@user-notify-devel-2ff1dc8d.iam.gserviceaccount.com \
aiosmtpd \
--debug --listen 127.0.0.1:8025 --nosetuid \
--class ucam_user_notify.aiosmtpd.SMTPHandler \
testing-development
Set the USER_NOTIFY_IMPERSONATE_SERVICE_ACCOUNT environment variable to
impersonate a Google Service Account when initialising the session.
Set the USER_NOTIFY_ROLE_SESSION_NAME environment variable to override the
default AWS IAM Role session name when initialising the session.
Set the USER_NOTIFY_ENVIRONMENT environment variable to use a non-production
instance of the User Notify service.
Important
When running in a sidecar container with workload identity you ordinarily
should not need to set any of the USER_NOTIFY_... environment variables.
Running the SMTP bridge from Python¶
Note
This is currently the only way to require LOGIN or PLAIN authentication
to the SMTP server.
The following is an example of a SMTP server which listens on a hard-coded bind address and port and forwards incoming email via User Notify. Authentication is configured with a static username and password.
#!/usr/bin/env python
"""
An example of creating an unauthenticated SMTP server which uses User
Notify to send email.
NOTE: ucam-user-notify must be installed with the 'smtp' extra in
order to use the SMTP server.
Once started, you can test the server via the examples/smtp_client.py
script.
"""
import logging
import time
from typing import Optional
from aiosmtpd.controller import Controller
from ucam_user_notify import Session
from ucam_user_notify.aiosmtpd import (
SMTPHandler,
UsernamePasswordAuthenticator,
)
# ID of service as registered with the user notify service.
SERVICE_ID = "testing-development"
# Host and port to bind SMTP server to.
SMTP_HOST = "127.0.0.1"
SMTP_PORT = 8025
# Credentials required for authentication.
USERNAME = "example-user"
PASSWORD = "example-password"
# If you're running this code from a Cloud Run service, do not use
# impersonation and instead set this to None or omit it.
IMPERSONATE_GOOGLE_SERVICE_ACCOUNT: Optional[str] = (
"service-testing@user-notify-devel-2ff1dc8d.iam.gserviceaccount.com"
)
# Create a logging object and configure logging.
LOG = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
# A session will perform some one-time authentication and fetch of
# configuration when constructed so that this overhead is not present
# for each call to send_email().
LOG.info("Creating User Notify session.")
session = Session.for_service(
SERVICE_ID,
impersonate_service_account=IMPERSONATE_GOOGLE_SERVICE_ACCOUNT,
# DO NOT SET environment WHEN YOU USE THIS LIBRARY. THIS IS ONLY
# HERE TO ENSURE THAT THIS EXAMPLE CANNOT SEND EMAIL OUTSIDE OF
# THE EMAIL SANDBOX.
environment="development",
)
if session.default_from_address is not None:
LOG.info(f"Default from address: {session.default_from_address}")
# Create the SMTP handler for the User Notify session and start the
# SMTP server.
LOG.info(f"Starting SMTP relay server on {SMTP_HOST}:{SMTP_PORT}.")
handler = SMTPHandler(session)
controller = Controller(
handler,
hostname=SMTP_HOST,
port=SMTP_PORT,
# We do require auth but we don't require TLS. This causes
# aiosmtpd to (rightfully) generate a warning.
authenticator=UsernamePasswordAuthenticator(USERNAME, PASSWORD),
auth_require_tls=False,
auth_required=True,
)
controller.start()
# Sleep while the server runs. Ensure that the server is stopped on,
# e.g., a KeyboardInterrupt.
try:
while True:
time.sleep(3600)
except KeyboardInterrupt:
LOG.info("Keyboard interrupt received.")
finally:
LOG.info("Stopping SMTP relay server.")
controller.stop()
# vim:tw=70
You can send a test email via this server using this client script:
#!/usr/bin/env python
"""
Companion to examples/smtp_server.py. An example of sending email via
the SMTP server.
"""
import smtplib
# Note that the From address must correspond to one which this service
# is allowed to send from.
client = smtplib.SMTP("127.0.0.1", 8025)
client.login("example-user", "example-password")
client.sendmail(
"testing@ses.devel.user-notify.gcp.uis.cam.ac.uk",
"success@simulator.amazonses.com",
"Subject: Test\n\nThis is a test email.",
)
client.quit()
# vim:tw=70