snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
89
zerver/management/commands/email_mirror.py
Normal file
89
zerver/management/commands/email_mirror.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""Cron job implementation of Zulip's incoming email gateway's helper
|
||||
for forwarding emails into Zulip.
|
||||
|
||||
https://zulip.readthedocs.io/en/latest/production/email-gateway.html
|
||||
|
||||
The email gateway supports two major modes of operation: An email
|
||||
server where the email address configured in EMAIL_GATEWAY_PATTERN
|
||||
delivers emails directly to Zulip, and this, a cron job that connects
|
||||
to an IMAP inbox (which receives the emails) periodically.
|
||||
|
||||
Run this in a cron job every N minutes if you have configured Zulip to
|
||||
poll an external IMAP mailbox for messages. The script will then
|
||||
connect to your IMAP server and batch-process all messages.
|
||||
|
||||
We extract and validate the target stream from information in the
|
||||
recipient address and retrieve, forward, and archive the message.
|
||||
|
||||
"""
|
||||
|
||||
import email.parser
|
||||
import email.policy
|
||||
import logging
|
||||
from collections.abc import Generator
|
||||
from email.message import EmailMessage
|
||||
from imaplib import IMAP4_SSL
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import CommandError
|
||||
from typing_extensions import override
|
||||
|
||||
from zerver.lib.email_mirror import logger, process_message
|
||||
from zerver.lib.management import ZulipBaseCommand, skip_unless_locked
|
||||
|
||||
## Setup ##
|
||||
|
||||
log_format = "%(asctime)s: %(message)s"
|
||||
logging.basicConfig(format=log_format)
|
||||
|
||||
formatter = logging.Formatter(log_format)
|
||||
file_handler = logging.FileHandler(settings.EMAIL_MIRROR_LOG_PATH)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
|
||||
def get_imap_messages() -> Generator[EmailMessage, None, None]:
|
||||
# We're probably running from cron, try to batch-process mail
|
||||
if (
|
||||
not settings.EMAIL_GATEWAY_BOT
|
||||
or not settings.EMAIL_GATEWAY_LOGIN
|
||||
or not settings.EMAIL_GATEWAY_PASSWORD
|
||||
or not settings.EMAIL_GATEWAY_IMAP_SERVER
|
||||
or not settings.EMAIL_GATEWAY_IMAP_PORT
|
||||
or not settings.EMAIL_GATEWAY_IMAP_FOLDER
|
||||
):
|
||||
raise CommandError(
|
||||
"Please configure the email mirror gateway in /etc/zulip/, "
|
||||
"or specify $ORIGINAL_RECIPIENT if piping a single mail."
|
||||
)
|
||||
mbox = IMAP4_SSL(settings.EMAIL_GATEWAY_IMAP_SERVER, settings.EMAIL_GATEWAY_IMAP_PORT)
|
||||
mbox.login(settings.EMAIL_GATEWAY_LOGIN, settings.EMAIL_GATEWAY_PASSWORD)
|
||||
try:
|
||||
mbox.select(settings.EMAIL_GATEWAY_IMAP_FOLDER)
|
||||
try:
|
||||
_status, num_ids_data = mbox.search(None, "ALL")
|
||||
for message_id in num_ids_data[0].split():
|
||||
_status, msg_data = mbox.fetch(message_id, "(RFC822)")
|
||||
assert isinstance(msg_data[0], tuple)
|
||||
msg_as_bytes = msg_data[0][1]
|
||||
yield email.parser.BytesParser(
|
||||
_class=EmailMessage, policy=email.policy.default
|
||||
).parsebytes(msg_as_bytes)
|
||||
mbox.store(message_id, "+FLAGS", "\\Deleted")
|
||||
mbox.expunge()
|
||||
finally:
|
||||
mbox.close()
|
||||
finally:
|
||||
mbox.logout()
|
||||
|
||||
|
||||
class Command(ZulipBaseCommand):
|
||||
help = __doc__
|
||||
|
||||
@override
|
||||
@skip_unless_locked
|
||||
def handle(self, *args: Any, **options: str) -> None:
|
||||
for message in get_imap_messages():
|
||||
process_message(message)
|
||||
Reference in New Issue
Block a user