Skip to content

feat: Mail Exchange#345

Merged
s-aga-r merged 23 commits into
frappe:developfrom
s-aga-r:mail-exchange
Jan 6, 2026
Merged

feat: Mail Exchange#345
s-aga-r merged 23 commits into
frappe:developfrom
s-aga-r:mail-exchange

Conversation

@s-aga-r
Copy link
Copy Markdown
Collaborator

@s-aga-r s-aga-r commented Dec 23, 2025

This is the JMAP-based implementation of #224. It enables importing and exporting emails using the JMAP protocol.

Import

  • eml
  • jmap
  • mbox
  • maildir
  • maildir-nested

Export

  • jmap
  • mbox?
  • maildir
  • maildir-nested?

Configurations

The following configuration values can be set in site_config.json:

  • mail_exchange_max_import: Maximum number of emails that can be imported in a single operation. Default: 1000
  • mail_exchange_max_export: Maximum number of emails that can be exported in a single operation. Default: 1000
  • mail_exchange_import_timeout: Timeout (in seconds) for the import job. Default: 3600
  • mail_exchange_export_timeout: Timeout (in seconds) for the export job. Default: 3600
  • mail_exchange_export_batch_size: Number of blobs downloaded concurrently during export. Default: 500

Manual Test

  • Export
    • jmap (.zip, .tgz, .tar.gz)
    • mbox (.zip, .tgz, .tar.gz)
    • maildir (.zip, .tgz, .tar.gz)
    • maildir-nested (.zip, .tgz, .tar.gz)
  • Import
    • eml (.eml, .zip, .tgz, .tar.gz)
    • jmap (.zip, .tgz, .tar.gz)
    • mbox (.zip, .tgz, .tar.gz)
    • maildir (.zip, .tgz, .tar.gz)
    • maildir-nested (.zip, .tgz, .tar.gz)

Notes

  1. Mailbox Creation Order
    Mailboxes must be created before creating the exchange. They are not automatically created based on the mbox filename or the Maildir nested directory structure, as there is an important edge case. If the directory structure indicates that mailbox X is the parent of Y, but on the server, Y is configured as the parent of X, this inconsistency will break any attempt to auto-create mailboxes based on the directory hierarchy.

  2. Received-At Timestamp Accuracy
    In Stalwart versions prior to 0.15.x, the Received-At timestamp may be incorrect and may match the import time instead. The receivedAt property in Metadata is read-only and is not set during email import, as some JMAP servers reject custom receivedAt values. Instead, this timestamp should be derived by parsing the most recent Received header from the message by the server.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Mail Exchange doctype that enables JMAP-based email import/export functionality, supporting multiple formats (eml, jmap, mbox, maildir, maildir-nested). It also refactors the existing Mail Data Exchange to use new directory utilities and improves error handling.

Key Changes

  • New client-side Mail Exchange doctype with JMAP-based import/export
  • Refactored directory management with separate paths for data vs mail exchange
  • Updated validation logic for Maildir and JMAP structures
  • Streamlined notification and error handling in both exchange doctypes

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
mail/utils/validation.py Updated validate_jmap_structure signature and improved Maildir validation logic
mail/utils/__init__.py Added separate directory functions for mail and data exchange operations
mail/server/doctype/mail_data_exchange/mail_data_exchange.py Refactored to use new directory utilities and simplified notification handling
mail/server/doctype/mail_data_exchange/mail_data_exchange.json Updated field layout and precision settings
mail/client/doctype/mail_exchange/mail_exchange.py New 914-line implementation with format loaders, exporters, and JMAP integration
mail/client/doctype/mail_exchange/mail_exchange.json New doctype configuration with import/export fields
mail/client/doctype/mail_exchange/mail_exchange.js Client-side form logic with retry functionality
mail/client/doctype/mail_exchange/mail_exchange_list.js List view configuration with status indicators
mail/client/doctype/mail_exchange/test_mail_exchange.py Test file placeholder following codebase conventions
mail/hooks.py Registered new permission handlers and scheduled tasks
Comments suppressed due to low confidence (1)

mail/utils/validation.py:205

  • The parameter required_files is of type list[str], but on line 199 it's being compared with or to a set. When a non-empty list is passed, it will always be truthy, but the logic should check if the list is None or empty and use the default set. This should be changed to use proper type checking or default parameter value. Additionally, the type annotation should be updated to list[str] | None to match the intended behavior.
def validate_jmap_structure(
	base_dir: str, required_files: list[str], raise_exception: bool = False
) -> list[str]:
	"""Validates a JMAP import directory. Returns a list of missing files or folders."""

	required_files = required_files or {
		"emails.json",
		"identities.json",
		"mailboxes.json",
		"sieve.json",
		"vacation.json",
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mail/client/doctype/mail_exchange/mail_exchange.py Outdated
Comment thread mail/server/doctype/mail_data_exchange/mail_data_exchange.py
Comment thread mail/server/doctype/mail_data_exchange/mail_data_exchange.py Outdated
Comment thread mail/client/doctype/mail_exchange/mail_exchange.py Outdated
Comment thread mail/client/doctype/mail_exchange/mail_exchange.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@s-aga-r s-aga-r marked this pull request as ready for review January 6, 2026 07:18
@s-aga-r s-aga-r requested a review from krantheman as a code owner January 6, 2026 07:18
@s-aga-r s-aga-r merged commit 2695e12 into frappe:develop Jan 6, 2026
3 checks passed
@s-aga-r s-aga-r deleted the mail-exchange branch January 7, 2026 05:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants