Mozilla-specific implementation of emails generated by Phabricator.
- Accessibility (emails should have screen reader support)
- Efficiency (emails should be fast to parse and triage)
- Pretty (emails should look nice 😎)
This daemon:
- Talks to Phabricator to get email event data
- Renders HTML and text emails based on this data
- Sends the rendered emails
This is a backend service that is deployed as a dockerflow-compatible
image called mozilla/phabricator-emails
.
Place a settings.ini
file in the /app
directory of the container.
There's an example production settings.ini
file in the repository called settings.production-example.ini
. You can
copy that one, then consult the configuration documentation below for additional details.
Finally, set an environment variable called PHABRICATOR_EMAILS_SETTINGS_PATH
to /app/settings.ini
.
To follow Amazon convention, the AWS credentials can be provided via
environment variable
or via settings.ini
, whichever is more convenient.
The database has to be populated with tables and synchronized with the current feed status of Phabricator.
Once settings.ini
has been set up, you can perform this initialization with: docker run $container prepare
Once first-time preparation is finished, and you're ready to run, do docker run $container service
.
It will loop every [phabricator].poll_gap_seconds
seconds, query
Phabricator for email events, and send them using the driver you specified with an [email-*]
section.
There may be changes to the the database schema, so you'll want to run phabricator-emails migrate
to upgrade
the database schema after updating the phabricator-emails
code.
For testing locally, you're going to need a couple things:
- Python 3.9.
- Phabricator and
Bugzilla running locally, probably via
suite
- Log into Phabricator as an admin
- From the main page, go to "More Applications" > "People" > "Create New User"
- Create a bot user called "email-bot"
- From the "People" application, click
email-bot
> "Manage" > "Edit Settings" > "Conduit API Tokens" - Create an API token for this user to use in
settings.ini
- A Postgresql database running with a database, user and password as named in the example
settings.ini
below - Open a command prompt
- Create a virtualenv:
python3 -m venv env
- Activate the virtualenv:
. env/bin/activate
- Install dependencies:
pip install -r requirements.txt
- Install the email CLI:
pip install -e .
export http_proxy=http://127.0.0.1:1080
so that python can see Phabricator from outside of Docker- Copy
settings.development-example.ini
tosettings.ini
, setphabricator.token
with the value from step 2.5.- You can adjust the behaviour to enhance your testing using the configuration reference below.
phabricator-emails prepare
to initialize the database tables and synchronize the feed position.- Finally, to run the service, do
phabricator-emails service
.
If changes are made to the database models, generate migrations:
alembic revision --autogenerate -m "<summary of changes>
- Edit the new alembic revision in
alembic/versions/
- Sometimes, Alembic isn't able to perfectly auto-generate migrations, especially around constraints, so it's necessary to manually verify the revision contents.
- Run
phabricator-emails migrate
locally to verify that the new migration works
Each setting with [!]
is a secret.
[phabricator]
; host (including protocol) of the Phabricator instance to fetch email events from
host=http://phabricator.test
; token for phabricator user with the name "email-bot"
token=[!] token
; time between each Phabricator poll
poll_gap_seconds=[optional] [default=60]
[bugzilla]
; host (including protocol) of the BMO instance to link to in the emails
host=http://bmo.test
[sentry]
; Sentry DSN
dsn=[!] [optional] https://[email protected]/123
[db]
; connection string to database with the format "postgresql://username:password@host/db_name"
; Note that the password is in this connection screen, so this should be a secret value
url=[!] postgresql://dev:[email protected]/phabricator_emails
[email]
; the "from" address of each email
from_address[email protected]
; chooses which email implementation to use. Possible options are "fs", "smtp" and "ses"
implementation=fs
; time before retrying to send an email if a temporary error is encountered
temporary_error_retry_delay_seconds=[optional] [default=30]
; one of the following three [email-*] sections need to be uncommented so that "phabricator-emails" knows
; which email driver to use.
; this implementation writes emails to the file system.
; This is useful for debugging content and HTML stying issues, but isn't a great end-to-end test
[email-fs]
; an absolute (or relative from the working directory) path where the email files should be stored
output_directory=[optional] [default=$CWD/output/]
; this implementation uses an SMTP server to test the sending/viewing of emails.
; It's useful for end-to-end testing since you can view the real emails in GMail/iOS Mail/etc. Additionally, this
; has the developer-specific "[email-smtp].send_to" option which causes all emails to send to a specific email
; address, regardless of Phabricator's intended recipient.
[email-smtp]
host=127.0.0.1
send_to=[optional] [email protected]
; sends emails using Amazon SES. This is the most-likely production configuration.
[email-ses]
aws_access_key_id=[!] [optional] token
aws_secret_access_key=[!] [optional] token
# The aws_session_token is only needed if using credentials that require MFA validation
# to be accepted. It can be obtained by running:
# $ aws sts get-session-token --serial-number <serial> --token-code <token>
# See the docs here: https://docs.aws.amazon.com/cli/latest/reference/sts/get-session-token.html
aws_session_token=[!] [optional] token
; this section is for local development options. If "phabricator-emails" sees the "[dev]" header, it will
; print logs in plaintext, rather than in JSON, which is far easier for debugging/readability.
;[dev]
; rather than asking Phabricator for events, read them from this file and send emails once, then exit.
; This value is either an absolute path or a relative path from the working directory.
; Note: Setting this means that the "phabricator.*", "dev.since_key" and "dev.story_limit" options will be ignored.
;file=[optional] example.json
;
; rather than fetching events from Phabricator and progressing through them, setting "since_key" forces
; "phabricator-emails" to query from the provided key (these keys are timestamps).
; This is useful if you want to end-to-end test a specific event that happened on Phabricator.
;since_key=[optional] 6809009593164319710
; Only fetch "story_limit" number of stories from Phabricator. Ignored unless "since_key" is set.
;story_limit=[optional] 20
phabricator-emails
has a plugin-like architecture to allow testing various parts of the pipeline.
Each time the pipeline is invoked, it will read some events from some source
, render the emails that are triggered
from that event, then send them via some mail
. Finally, according to the progress
implementation, this
pipeline may either be run once, or continually in a loop.
The implementations are chosen based on the contents of settings.ini
.
Some example configuration use cases are:
- I'm end-to-end testing a specific event. I'm reading from my local Phabricator instance (
PhabricatorSource
), but I want to keep re-sending the same event (RunOnceWorker
). Finally, I want to see how these emails look in GMail, so I'm sending them to my real inbox using a local SMTP server (SmtpMail
) - The
RevisionCreated
email has a styling issue that I want to diagnose. I've created some mock data to represent the data and put it in a file (FileSource
,RunOnceWorker
). The CSS issue is reproducible in-browser, so I'll just output it to an HTML file and inspect it there (FsMail
). - I'm running in production, so I want to read real events from Phabricator (
PhabricatorSource
,PhabricatorWorker
). In production, we use Amazon's "Simple Email Service" (SES), so I useSesMail
.
A source fetches some number of "events". Each event describes an action that took place on Phabricator, such as a revision being created or a user being pinged in a comment. Each event may cause multiple emails: for example, a revision being updated will trigger an email for all reviewers.
Implementation name | Purpose |
---|---|
PhabricatorSource |
Fetches events from a real Phabricator instance |
FileSource |
Reads raw events in JSON form from the file system |
In production, each event should only trigger emails once. However, in testing, it's useful to iterate on an email,
sending it multiple times. Accordingly, the Worker
implementations provide this flexibility.
Implementation name | Purpose |
---|---|
PhabricatorWorker |
Continually runs and re-runs the pipeline, remembering its position in the Phabricator feed. Waits for a set amount of time between pipeline runs |
RunOnceWorker |
Runs the pipeline once from a specific position in the feed |
Submits rendered emails, either into the internet or locally for debugging.
Implementation name | Purpose |
---|---|
SesMail |
Submits emails via Amazon's "Simple Email Service" (SES) |
SmtpMail |
Submits emails via an SMTP server. Useful if you don't have Amazon credentials, but still want to test real email clients |
FsMail |
Writes the raw emails into the output/ folder in the working directory. Is useful for debugging email headers or styling issues that aren't caused by real email clients |