Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/crowdin_download.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
branches:
- 'release/**'

permissions:
contents: read

jobs:

synchronize-with-crowdin:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/crowdin_upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
branches:
- main

permissions:
contents: read

jobs:

synchronize-with-crowdin:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Build and Push Container Image

permissions:
contents: read

"on":
workflow_call:
inputs:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/messages-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ name: Build and publish OCI images
tags:
- 'v*'

permissions:
contents: read

jobs:

docker-publish-mta-in:
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/messages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ name: Lint and tests
branches:
- '*'

permissions:
contents: read

env:
COMPOSE_BAKE: true

Expand Down Expand Up @@ -95,6 +98,46 @@ jobs:
- name: Build frontend
run: make build-front

lint-client-bridge:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Create env files
run: make create-env-files
- name: Run client-bridge linting
run: make lint-client-bridge
Comment thread
sylvinus marked this conversation as resolved.
Outdated

test-client-bridge:
Comment thread Fixed
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Create env files
run: make create-env-files
- name: Run client-bridge tests
run: make test-client-bridge

test-mta-in:
Comment thread Fixed
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Create env files
run: make create-env-files
- name: Run mta-in tests
run: make test-mta-in

test-socks-proxy:
Comment thread Fixed
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Create env files
run: make create-env-files
- name: Run socks-proxy tests
run: make test-socks-proxy

Comment thread
coderabbitai[bot] marked this conversation as resolved.
check-api-state:
runs-on: ubuntu-latest
steps:
Expand Down
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ create-env-files: \
env.d/development/frontend.local \
env.d/development/mta-in.local \
env.d/development/mta-out.local \
env.d/development/client-bridge.local \
env.d/development/socks-proxy.local
.PHONY: create-env-files

Expand Down Expand Up @@ -178,7 +179,8 @@ lint: \
lint-front \
typecheck-front \
lint-mta-in \
lint-mta-out
lint-mta-out \
lint-client-bridge
.PHONY: lint

lint-check: ## run all linters in check mode (no auto-fix)
Expand Down Expand Up @@ -231,6 +233,10 @@ lint-mta-out: ## lint mta-out python sources
$(COMPOSE_RUN) --rm -e EXEC_CMD_ONLY=true mta-out-test ruff format .
.PHONY: lint-mta-out

lint-client-bridge: ## lint client-bridge python sources
$(COMPOSE_RUN) --rm -e EXEC_CMD_ONLY=true client-bridge-test ruff format .
.PHONY: lint-client-bridge

# -- Tests

test: ## run all tests
Expand All @@ -239,6 +245,7 @@ test: \
test-front \
test-mta-in \
test-mta-out \
test-client-bridge \
test-mpa \
test-socks-proxy
.PHONY: test
Expand Down Expand Up @@ -280,6 +287,10 @@ test-mta-out: ## run the mta-out tests
@$(COMPOSE) run --build --rm mta-out-test
.PHONY: test-mta-out

test-client-bridge: ## run the client-bridge tests
@$(COMPOSE) run --build --rm client-bridge-test
.PHONY: test-client-bridge

test-mpa: ## run the mpa tests
@$(COMPOSE) run --build --rm mpa-test
.PHONY: test-mpa
Expand Down Expand Up @@ -578,3 +589,7 @@ deps-lock-mta-in: ## lock the dependencies
deps-lock-mta-out: ## lock the dependencies
@$(COMPOSE) run --rm --build mta-out-uv uv lock
.PHONY: deps-lock-mta-out

deps-lock-client-bridge: ## lock the dependencies
@$(COMPOSE) run --rm --build client-bridge-uv uv lock
.PHONY: deps-lock-client-bridge
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ It features a [MTA](https://en.wikipedia.org/wiki/Message_transfer_agent) based
* (soon) 👉 Assign threads to specific users

### Based on standards
* 🔑 OpenID Connect for all user accounts. Plug any identity provider, including Keycloak.
* 📬 SMTP in and out.
* ❌ No POP3 or IMAP client support, by design. We're building for the future, not the (unsecure) past!
* ✅ JMAP-inspired data model. Full support could be added.
* 🔑 OpenID Connect for all user accounts as the primary authentication method. Plug any identity provider, including Keycloak.
* 📬 SMTP in and out (server-to-server).
* ✅ JMAP-inspired data model. JMAP-compliant endpoint [in progress](https://github.com/suitenumerique/messages/pull/479).
* 📮 Optional IMAP and SMTP client access via the [client bridge](/src/client-bridge/), for users who prefer traditional email clients like Thunderbird or mobile phones. Uses app-specific passwords with configurable roles.


### Self-host
* 🚀 Messages is designed to be installed on the cloud or on your own servers.
Expand Down Expand Up @@ -146,6 +147,8 @@ When running the project, the following services are available:
| **SOCKS Proxy** | 8916 | SOCKS5 proxy | `user1` / `pwd1` |
| **Mailcatcher (SMTP)** | 8917 | SMTP server | No auth required |
| **MPA (Rspamd)** | 8918 | Spam filtering service | `password` |
| **Client Bridge (IMAP)** | 8919 | IMAP server for email clients | App-specific password |
| **Client Bridge (SMTP)** | 8920 | SMTP submission for email clients | App-specific password |


### OpenAPI client
Expand Down
43 changes: 43 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,49 @@ services:
target: uv
pull_policy: build

client-bridge:
build:
context: src/client-bridge
target: runtime-prod
env_file:
- env.d/development/client-bridge.defaults
- env.d/development/client-bridge.local
ports:
- "8919:143"
- "8920:587"
depends_on:
- backend-dev

client-bridge-test:
profiles:
- tools
build:
context: src/client-bridge
target: runtime-dev
env_file:
- env.d/development/client-bridge.defaults
- env.d/development/client-bridge.local
environment:
- EXEC_CMD=true
- IMAP_HOST=localhost
- IMAP_PORT=1143
- SMTP_HOST=localhost
- SMTP_PORT=1587
- MOCK_API_PORT=8765
command: pytest -vvs tests/
volumes:
- ./src/client-bridge:/app

client-bridge-uv:
profiles:
- tools
volumes:
- ./src/client-bridge:/app
build:
context: src/client-bridge
target: uv
pull_policy: build

mta-out:
build:
context: src/mta-out
Expand Down
3 changes: 3 additions & 0 deletions env.d/development/backend.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ AI_MODEL=
FEATURE_AI_SUMMARY=False
FEATURE_AI_AUTOLABELS=False

# Client bridge (IMAP/SMTP email client access)
FEATURE_CLIENTBRIDGE=False

Comment thread
sylvinus marked this conversation as resolved.
# Third-party services
# Drive - https://github.com/suitenumerique/drive
DRIVE_BASE_URL=
8 changes: 8 additions & 0 deletions env.d/development/client-bridge.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
MESSAGES_API_BASE_URL=http://backend-dev:8000/api/v1.0/
CLIENTBRIDGE_API_SECRET=my-shared-secret-clientbridge-at-least-32-bytes
ENABLE_IMAP=true
IMAP_HOST=0.0.0.0
IMAP_PORT=143
ENABLE_SMTP=true
SMTP_HOST=0.0.0.0
SMTP_PORT=587
2 changes: 1 addition & 1 deletion src/backend/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs=0

# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=pylint_django
load-plugins=pylint_django,pylint_custom

# Pickle collected data for later comparisons.
persistent=yes
Expand Down
1 change: 0 additions & 1 deletion src/backend/README.md

This file was deleted.

65 changes: 65 additions & 0 deletions src/backend/core/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,60 @@
}
}
},
"/api/v1.0/mailboxes/{mailbox_id}/channels/{id}/rotate-password/": {
"post": {
"operationId": "mailboxes_channels_rotate_password_create",
"description": "Manage integration channels for a mailbox",
"parameters": [
{
"in": "path",
"name": "id",
"schema": {
"type": "string"
},
"required": true
},
{
"in": "path",
"name": "mailbox_id",
"schema": {
"type": "string",
"format": "uuid"
},
"required": true
}
],
"tags": [
"channels"
],
"security": [
{
"cookieAuth": []
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RotatePasswordResponse"
}
}
},
"description": ""
},
"400": {
"description": "Channel type does not support password rotation"
},
Comment on lines +2894 to +2896
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Expose the custom 400 payload for unsupported channels.

The backend returns a JSON body with detail here, but the schema only has a description. That leaves the documented contract out of sync with src/backend/core/api/viewsets/channel.py:136-144.

🛠️ Proposed response schema
                     "400": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "type": "object",
+                                    "properties": {
+                                        "detail": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": [
+                                        "detail"
+                                    ]
+                                }
+                            }
+                        },
                         "description": "Channel type does not support password rotation"
                     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"400": {
"description": "Channel type does not support password rotation"
},
"400": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"detail": {
"type": "string"
}
},
"required": [
"detail"
]
}
}
},
"description": "Channel type does not support password rotation"
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/api/openapi.json` around lines 2894 - 2896, The OpenAPI
spec's 400 response for the channel endpoint only has a description but should
document the JSON body returned by src/backend/core/api/viewsets/channel.py
(around the logic at lines 136-144) which returns {"detail": "..."}; update the
400 response schema in openapi.json for that endpoint to an object schema with a
string property "detail" (required) and appropriate example, so the documented
contract matches the actual response from the Channel view (ChannelViewSet / the
channel rotation/password endpoints).

"403": {
"description": "Permission denied"
},
"404": {
"description": "Channel not found"
}
}
}
Comment thread
sylvinus marked this conversation as resolved.
},
"/api/v1.0/mailboxes/{mailbox_id}/image-proxy/": {
"get": {
"operationId": "mailboxes_image_proxy_list",
Expand Down Expand Up @@ -7780,6 +7834,17 @@
"one_time_password"
]
},
"RotatePasswordResponse": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
},
"SendMessageRequest": {
"type": "object",
"description": "Serializer for sending messages.",
Expand Down
Loading