SMTP relay PaaS plugin for the Exordos MetaPaaS runtime (exim4)
Project description
mailaas: SMTP Relay PaaS Plugin for MetaPaaS
exim4 SMTP submission server packaged as a MetaPaaS plugin,
following the same pattern as metapaas_s3.
Architecture
metapaas-cp
└── mail plugin (exordos_paas_mail)
├── CP: MailInstance + MailAccount models, REST API, migrations
├── infra_builder: CoreInfraBuilder → NodeSet + Config on core
└── paas_builder: MailInstanceBuilder → MailInstanceNode → DP agent
mailaas-dp-<uuid> (VM)
└── exim4 (SMTP 25/465/587 — STARTTLS + auth)
├── /etc/exordos_metapaas/mail.env ← delivered by CP (MAIL_DOMAIN)
├── /etc/exim4/passwd ← managed by DP agent (lsearch auth)
└── exordos-universal-agent ← MailCapabilityDriver
Quick Start
Build
make build \
REPOSITORY=http://10.20.0.1:8080/exordos-elements \
INDEX_URL=http://10.20.0.1:8080/simple/
Produces:
output/images/exordos-metapaas-mail-dp.raw.zst(DP image)output/manifests/mailaas.yaml(element manifest)
Install on running metapaas
make install
PluginReconciler on metapaas-cp installs exordos_paas_mail via pip and
activates the /v1/types/mail/ route.
Create Instance
curl -X POST http://metapaas-cp:8080/v1/types/mail/instances/ \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"name": "mail-prod",
"project_id": "4d657461-0000-0000-0000-000000000002",
"domain": "example.com",
"cpu": 2,
"ram": 2048,
"disk_size": 20,
"version": "/v1/types/mail/versions/<version-uuid>"
}'
Create SMTP Account
# Generate SHA512-crypt hash (compatible with exim4 crypteq)
HASH=$(openssl passwd -6 "mypassword")
curl -X POST http://metapaas-cp:8080/v1/types/mail/instances/<uuid>/accounts/ \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d "{
\"username\": \"alice\",
\"password_hash\": \"${HASH}\",
\"project_id\": \"4d657461-0000-0000-0000-000000000002\",
\"instance\": \"/v1/types/mail/instances/<uuid>\"
}"
Within seconds the DP agent writes the account to /etc/exim4/passwd and
reloads exim4. The user can then authenticate via SMTP AUTH (PLAIN/LOGIN over
STARTTLS on port 587 or SMTPS on port 465).
Hashes stored with a Dovecot {SHA512-CRYPT} scheme prefix are accepted — the
driver strips the prefix before writing to exim4's passwd file.
Configure DNS
For mail to be accepted by other servers you must publish a few DNS records for
your domain (referred to below as $1, e.g. example.com).
-
DKIM — the key is generated on the node by the configure script. Once the instance is
ACTIVE, read it from the API:curl -s http://metapaas-cp:8080/v1/types/mail/instances/<uuid> \ -H 'Authorization: Bearer <token>' | jq -r '{dkim_selector, dkim_public_key}'
Publish it as a
TXTrecord at<dkim_selector>._domainkey.$1(default selector:platform), with thedkim_public_keyvalue as data. The raw record is also on the node at/etc/exim4/dkim/platform.txt. -
SPF — name:
@(the domain itself), type:TXT, TTL: 3600, data:"v=spf1 ip4:YOUR_SERVER_IP/32 a mx ~all" -
DMARC — name:
_dmarc, type:TXT, TTL: 3600, data:"v=DMARC1; p=none; pct=100; adkim=s; aspf=s" -
PTR — set the reverse record for your server IP to
$1 -
MX — ensure an
MXrecord exists (e.g. name:@, type:MX, data:$1)
API Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /v1/types/mail/instances/ |
Create mail server |
| GET | /v1/types/mail/instances/ |
List instances |
| GET | /v1/types/mail/instances/<uuid> |
Get instance |
| PATCH | /v1/types/mail/instances/<uuid> |
Update (cpu/ram/disk_size) |
| DELETE | /v1/types/mail/instances/<uuid> |
Delete |
| GET | /v1/types/mail/versions/ |
List DP image versions |
| POST | /v1/types/mail/instances/<uuid>/accounts/ |
Create account |
| GET | /v1/types/mail/instances/<uuid>/accounts/ |
List accounts |
| PATCH | /v1/types/mail/instances/<uuid>/accounts/<uuid> |
Update (active, password_hash) |
| DELETE | /v1/types/mail/instances/<uuid>/accounts/<uuid> |
Delete account |
Field permissions
| Field | Create | Read | Update |
|---|---|---|---|
domain |
RW | RO | RO |
status |
— | RO | RO |
ipsv4 |
— | RO | RO |
dkim_public_key |
— | RO | RO |
dkim_selector |
— | RO | RO |
password_hash |
RW | hidden | RW |
username |
RW | RO | RO |
active |
RW | RW | RW |
Repository Layout
.
├── exordos_paas_mail/
│ ├── constants.py # MAIL_ENV_FILE, EXIM4_PASSWD_FILE paths
│ ├── models.py # MailVersion, MailInstance, MailAccount (restalchemy)
│ ├── controllers.py # REST controllers (gcl_iam policy-based)
│ ├── routes.py # Route tree (instances/accounts, versions)
│ ├── definition.py # MailDefinition (PaaSDefinition contract)
│ ├── infra_models.py # MailInstance + infra (NodeSet + Config)
│ ├── infra_builder.py # CoreInfraBuilder (creates VMs + delivers mail.env)
│ ├── paas_models.py # MailInstanceNode (target resource for DP agent)
│ ├── paas_builder.py # MailInstanceBuilder (maps accounts → DP payload)
│ ├── driver.py # MailCapabilityDriver: writes /etc/exim4/passwd
│ ├── utils.py # remove_nested_dm helper
│ ├── migrations/
│ │ ├── 0000-init-mail.py # Creates mail_versions, mail_instances, mail_accounts
│ │ └── 0001-drop-root-password.py
│ └── tests/
│ ├── unit/ # Unit tests (models, driver)
│ └── functional/ # E2E tests (prepare_env.py + SMTP auth tests)
├── exordos/
│ ├── exordos.yaml # Build config (deps + elements + DP image)
│ ├── images/
│ │ ├── dp_install.sh # Packer: install exim4 + configure script + agent
│ │ └── dp_bootstrap.sh # First-boot: persistent disk + start configure service
│ └── manifests/
│ ├── mailaas.yaml.j2 # Element manifest: type reg + IAM + DP version
│ └── example_mail.yaml.j2 # Example consumer element
├── etc/
│ ├── systemd/
│ │ ├── exordos-metapaas-mail-configure.service # Configures exim4 from mail.env
│ │ └── exordos-metapaas-mail-agent.service # Universal agent (MailCapabilityDriver)
│ └── exordos_metapaas/
│ ├── logging.yaml
│ └── metapaas_mail_agent.conf
├── .github/workflows/
│ ├── tests.yaml # Lint (ruff) on every push
│ └── func_tests.yaml # Full e2e: bootstrap core + deploy + SMTP tests
├── pyproject.toml
├── tox.ini
└── Makefile
Development
make test # Unit tests via tox
make lint # ruff check
make format # ruff format
make typecheck # mypy
make functional # E2E tests (needs live stand)
Running functional tests manually
python exordos_paas_mail/tests/functional/prepare_env.py \
--metapaas-dir ../exordos_metapaas \
--project-dir . \
--output-dir /tmp/mail-build \
--endpoint http://10.20.0.2:11010 \
--username admin --password <pass>
# Use env vars printed by prepare_env.py, then:
tox -e py312-functional
Key differences from metapaas_s3
| Aspect | s3aas | mailaas |
|---|---|---|
| DP software | RustFS | exim4 (SMTP relay) |
| Instance children | Bucket, Policy, User, AccessKey | Account |
| Replicas | 1–16 | Always 1 |
| Config delivered | rustfs.env |
mail.env (domain only) |
| DP auth state | S3 access keys | /etc/exim4/passwd (SHA512-crypt) |
| On-change | systemctl restart rustfs |
systemctl restart mail-configure |
| DP agent state file | s3_meta.json |
mail_meta.json |
| uuid5 name | s3aas |
mailaas |
References
- MetaPaaS design:
../exordos_metapaas/DESIGN.md - How to build new PaaS:
../exordos_s3/HOW_TO_BUILD_NEW_PAAS.md - Working reference:
../exordos_s3/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exordos_mail-0.0.2.tar.gz.
File metadata
- Download URL: exordos_mail-0.0.2.tar.gz
- Upload date:
- Size: 171.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
856879ecc5ab6fb7bb8fad8ce9060db80d7117609637de38bdb4512afc94fa5f
|
|
| MD5 |
ac351a2a21174975519506d6d394f622
|
|
| BLAKE2b-256 |
21317b57b5de79847125b5059ee0b9875914ce13e7bfdd159e4129ffed6b6885
|
Provenance
The following attestation bundles were made for exordos_mail-0.0.2.tar.gz:
Publisher:
publish-to-pypi.yml on exordos/exordos_mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exordos_mail-0.0.2.tar.gz -
Subject digest:
856879ecc5ab6fb7bb8fad8ce9060db80d7117609637de38bdb4512afc94fa5f - Sigstore transparency entry: 1886188107
- Sigstore integration time:
-
Permalink:
exordos/exordos_mail@f6e69668b40319ad29a6a900300929bbc874ecb0 -
Branch / Tag:
refs/tags/0.0.2 - Owner: https://github.com/exordos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@f6e69668b40319ad29a6a900300929bbc874ecb0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file exordos_mail-0.0.2-py3-none-any.whl.
File metadata
- Download URL: exordos_mail-0.0.2-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e72f385efe6de0bc44cbda826d8e4181b8a592c6407f2904882d280d92129a09
|
|
| MD5 |
3b9298db710d3441abe879fa7110aff0
|
|
| BLAKE2b-256 |
1274e56dc5dcbee49e1fa403830ced674043b5130505569382ac18d654e4370f
|
Provenance
The following attestation bundles were made for exordos_mail-0.0.2-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on exordos/exordos_mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
exordos_mail-0.0.2-py3-none-any.whl -
Subject digest:
e72f385efe6de0bc44cbda826d8e4181b8a592c6407f2904882d280d92129a09 - Sigstore transparency entry: 1886188135
- Sigstore integration time:
-
Permalink:
exordos/exordos_mail@f6e69668b40319ad29a6a900300929bbc874ecb0 -
Branch / Tag:
refs/tags/0.0.2 - Owner: https://github.com/exordos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@f6e69668b40319ad29a6a900300929bbc874ecb0 -
Trigger Event:
push
-
Statement type: