Skip to content

Fetches feed data from Phabricator, then renders and sends emails

License

Notifications You must be signed in to change notification settings

mozilla-conduit/phabricator-emails

Repository files navigation

Phabricator Emails

Mozilla-specific implementation of emails generated by Phabricator.

Screenshot of an example email

Goals

  • Accessibility (emails should have screen reader support)
  • Efficiency (emails should be fast to parse and triage)
  • Pretty (emails should look nice 😎)

Technical Information

This daemon:

  1. Talks to Phabricator to get email event data
  2. Renders HTML and text emails based on this data
  3. Sends the rendered emails

Production Usage

This is a backend service that is deployed as a dockerflow-compatible image called mozilla/phabricator-emails.

Configuration

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.

Amazon credentials

To follow Amazon convention, the AWS credentials can be provided via environment variable or via settings.ini, whichever is more convenient.

First-time preparations

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

Run

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.

Updating deployments

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.

Development

For testing locally, you're going to need a couple things:

  1. Python 3.9.
  2. Phabricator and Bugzilla running locally, probably via suite
    1. Log into Phabricator as an admin
    2. From the main page, go to "More Applications" > "People" > "Create New User"
    3. Create a bot user called "email-bot"
    4. From the "People" application, click email-bot > "Manage" > "Edit Settings" > "Conduit API Tokens"
    5. Create an API token for this user to use in settings.ini
  3. A Postgresql database running with a database, user and password as named in the example settings.ini below
  4. Open a command prompt
  5. Create a virtualenv: python3 -m venv env
  6. Activate the virtualenv: . env/bin/activate
  7. Install dependencies: pip install -r requirements.txt
  8. Install the email CLI: pip install -e .
  9. export http_proxy=http://127.0.0.1:1080 so that python can see Phabricator from outside of Docker
  10. Copy settings.development-example.ini to settings.ini, set phabricator.token with the value from step 2.5.
  11. phabricator-emails prepare to initialize the database tables and synchronize the feed position.
  12. Finally, to run the service, do phabricator-emails service.

Schema changes

If changes are made to the database models, generate migrations:

  1. alembic revision --autogenerate -m "<summary of changes>
  2. 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.
  1. Run phabricator-emails migrate locally to verify that the new migration works

Configuration Reference

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

Implementation

Diagram of the workflow and components

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 use SesMail.

Source

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

Worker

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

Mail

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

About

Fetches feed data from Phabricator, then renders and sends emails

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •