This page has developer documentation on the Zulip email system. If you're trying to configure your server to send email, you might be looking for our guide to sending outgoing email. If you're trying to configure an email integration to receive incoming email (e.g. so that users can reply to missed message emails via email), you might be interested in our instructions for setting up an email integration.

On to the documentation. Zulip's email system is fairly straightforward, with only a few things you need to know to get started.

  • All email templates are in templates/zerver/emails/. Each email has three template files: <template_prefix>.subject, <template_prefix>.txt, and <template_prefix>.html. Email templates, along with all other templates in the templates/ directory, are Jinja2 templates.
  • Most of the CSS and HTML layout for emails is in email_base.html. Note that email has to ship with all of its CSS and HTML, so nothing in static/ is useful for an email. If you're adding new CSS or HTML for an email, there's a decent chance it should go in email_base.html.
  • All email is eventually sent by zerver.lib.send_email.send_email. There are several other functions in zerver.lib.send_email, but all of them eventually call the send_email function. The most interesting one is send_future_email. The ScheduledEmail entries are eventually processed by a supervisor job that runs zerver/management/commands/
  • A good way to find a bunch of example email pathways is to git grep for zerver/emails in the zerver/ directory.

One slightly complicated decision you may have to make when adding an email is figuring out how to schedule it. There are 3 ways to schedule email.

  • Send it immediately, in the current Django process, e.g. by calling send_email directly. An example of this is the confirm_registration email.
  • Add it to a queue. An example is the invitation email.
  • Send it (approximately) at a specified time in the future, using send_future_email. An example is the followup_day2 email.

Email takes about a quarter second per email to process and send. Generally speaking, if you're sending just one email, doing it in the current process is fine. If you're sending emails in a loop, you probably want to send it from a queue. Documentation on our queueing system is available here.

Development and testing

All the emails sent in the development environment can be accessed by visiting /emails in the browser. The way that this works is that we've set the email backend (aka what happens when you call the email .send() method in Django) in the development environment to be our our custom backend, EmailLogBackEnd. It does the following:

  • Logs any sent emails to var/log/email_content.log. This log is displayed by the /emails endpoint (e.g.
  • Print a friendly message on console advertising /emails to make this nice and discoverable.

While running the backend test suite, we use django.core.mail.backends.locmem.EmailBackend as the email backend. The locmem backend stores messages in a special attribute of the django.core.mail module, "outbox". The outbox attribute is created when the first message is sent. It’s a list with an EmailMessage instance for each message that would be sent.