neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
Camelmailer 제품에서 보내야 하는 메일 발송
The mail your product has to send: receipts, password resets, magic links, alerts. One JSON API and an SMTP port, backed by a single Rust binary and PostgreSQL. Self-hosted or on the EU cloud.
개발 도구Rust · TypeScriptGitHub @camelmailer온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
Open-source transactional email in Rust. Self-host it or use the EU cloud. SMTP and HTTP APIs, DKIM/SPF, inbound routing, webhooks, tracking, and a full dashboard.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A headless, API-first mail delivery platform in Rust. SMTP in and out,
HTTP APIs for everything — sending, templates, message streams, statistics,
bounces, inbound routing, webhooks, tracking — backed by a single PostgreSQL
database with row-level-security tenant isolation. User accounts with
2FA, organization-level RBAC, invitations, OIDC single sign-on, an auth
audit log and CORS make it frontend- and enterprise-ready out of the box.
One small binary runs every process role. MIT licensed; born as a full
Rust rewrite of Postal.README 전문 보기 ↗A headless, API-first mail delivery platform in Rust. SMTP in and out, HTTP APIs for everything — sending, templates, message streams, statistics, bounces, inbound routing, webhooks, tracking — backed by a single PostgreSQL database with row-level-security tenant isolation. User accounts with 2FA, organization-level RBAC, invitations, OIDC single sign-on, an auth audit log and CORS make it frontend- and enterprise-ready out of the box. One small binary runs every process role. MIT licensed; born as a full Rust rewrite of Postal.
Web dashboard included: web/app is a Next.js (shadcn/ui) admin app
covering the entire API — registration and login with 2FA/SSO,
organizations and roles, servers with all resources, sending and message
browsing. The public OpenAPI spec documents
all endpoints. A library of 20 ready-to-clone transactional email
templates lives in templates/. See
web/README.md.
Three equivalent ways to run the full stack — pick one:
| Best for | You need | |
|---|---|---|
| Prebuilt Docker | fastest start, no toolchain | Docker only |
| Debian / Ubuntu | bare-metal / VM, systemd | a PostgreSQL |
| From source | hacking on Camelmailer | Docker (or Rust) |
One file, no repo clone — pulls ghcr.io/camelmailer/camelmailer:
mkdir camelmailer && cd camelmailer
curl -fsSLO https://raw.githubusercontent.com/camelmailer/camelmailer/main/install/docker-compose.yml
echo "POSTGRES_PASSWORD=$(openssl rand -hex 16)" > .env
docker compose up -d
docker compose exec web camelmailer make-user [email protected] Ada Ops --admin
Multi-arch (amd64 + arm64); pin a release with CAMELMAILER_VERSION=vX.Y.Z
in .env.
latest is the latest tagged release; edge follows main.
Native amd64/arm64 packages with systemd units, from the releases page:
sudo dpkg -i camelmailer_*.deb # binary, units, /etc/camelmailer/
sudo editor /etc/camelmailer/camelmailer.yml # point it at your PostgreSQL
sudo systemctl enable --now camelmailer-web camelmailer-smtp camelmailer-worker
Step-by-step (incl. PostgreSQL setup and bootstrap): docs/install-deb.md.
git clone https://github.com/camelmailer/camelmailer && cd camelmailer
cp .env.example .env # set POSTGRES_PASSWORD
docker compose up -d --build
curl http://localhost:5000/health
docker compose exec web camelmailer make-admin-api-key ops
Each path gives you PostgreSQL + migrations + the HTTP API (:5000) + SMTP
(:25) + the delivery worker; the Docker paths also start the web
dashboard on :3000 (image ghcr.io/camelmailer/camelmailer-web, a Next.js
server that proxies to the API — sign in with the make-user account). Follow docs/quickstart.md
for the five-minute zero-to-first-mail walkthrough, and
docs/configuration.md for DKIM, DNS records,
TLS, and the production checklist. Accounts, roles and SSO are covered in
docs/authentication.md.
The full documentation lives in docs/, with a guide per feature area: sending, message streams, templates and tracking; the marketing surface in broadcast streams, campaigns and suppressions; deliverability in sending domains, DMARC and IP pools; and integration through inbound routing and webhooks.
Test-first, throughout: protocol state machines, storage traits with
in-memory and PostgreSQL implementations kept in behavioural lockstep,
router tests for every endpoint, and PostgreSQL integration tests for the
row-level-security tenant isolation. CI enforces cargo fmt,
clippy -D warnings and the full suite; releases are cut from tags and
refuse to publish unless everything is green (see
CHANGELOG.md).
| Crate | What |
|---|---|
camelmailer-config | YAML configuration with validated schema and full defaults; $config-file-root substitution; Postal-compatible config files load unchanged |
camelmailer-core | Domain model + storage traits (Store for SMTP, async AdminStore, ServerStore, AuthStore, MessageSink) with in-memory implementations for tests; auth primitives (argon2, TOTP, tokens); safe Mustache-subset template renderer |
camelmailer-db | PostgreSQL implementations of all traits; embedded sqlx migrations; row-level-security tenant isolation (see below); delivery queue |
camelmailer-smtp | SMTP server: pure protocol state machine (no I/O in the session), tokio TCP listener, STARTTLS termination via rustls |
camelmailer-worker | Delivery worker: SKIP LOCKED dequeuer, SMTP sending (relays/MX) with opportunistic outbound STARTTLS and IP-pool source addresses, suppression holds, rspamd/ClamAV inspection, DKIM signing, open/click tracking rewrite, HTTP endpoint delivery, webhook queue with retries + RSA signing |
camelmailer-api | axum HTTP API: Server API (/api/v2/server, sending + message data), Management API (/api/v2/admin, all resources, RBAC-scoped for user sessions), Accounts API (/api/v2/auth, login/2FA/registration/resets/invitations/OIDC), public tracking endpoints, platform app-mail |
camelmailer (bin) | The single binary/CLI: web-server, smtp-server, worker, initialize, make-user, make-admin-api-key, version |
Camelmailer uses a single PostgreSQL database; tenant (mail-server)
isolation on the shared messages table is enforced by the database
itself via row-level security:
set_config('camelmailer.server_id', $1, true).migrations/0002_rls.sql) filters reads (USING) and rejects
writes (WITH CHECK) outside that context. Queries carry no
WHERE server_id filters — isolation does not depend on application code
remembering to scope.FORCE ROW LEVEL SECURITY keeps even the table owner subject to the
policy; only a superuser or an explicit BYPASSRLS role (reserved for a
future cross-tenant queue worker) can cross tenants.The RLS behaviour is covered by integration tests against a real PostgreSQL
(crates/camelmailer-db/tests/pg_tests.rs): tenant-scoped reads, hidden
rows without context, rejected cross-tenant writes, and unfiltered
UPDATE/DELETE confined to the tenant — plus an end-to-end test driving a
full SMTP session into Postgres. Set CAMELMAILER_TEST_DATABASE_URL (a role
with CREATEDB) to run them; they skip otherwise. Each test creates its own
throwaway database and runs the embedded migrations.
The SMTP session is a pure protocol state machine covering PROXY protocol,
HELO/EHLO capability framing, STARTTLS gating of AUTH,
AUTH PLAIN / LOGIN / CRAM-MD5, MAIL FROM (AUTH= stripping), RCPT TO with all
five routing branches (return-path, custom return-path prefix, route domain,
authenticated relay, route match, SMTP-IP fallback with longest-prefix
matching), DATA with dot-unstuffing, header capture with continuation lines,
bare-LF . rejection, maximum message size, mail-loop detection, and
From/Sender domain authentication — backed by an extensive session test
suite (crates/camelmailer-smtp/tests/session_tests.rs).
cargo test # all crates (Postgres tests skip without CAMELMAILER_TEST_DATABASE_URL)
CAMELMAILER_TEST_DATABASE_URL=postgres://user:pass@localhost:5432/cm_test cargo test
cargo clippy --workspace --all-targets
cargo run -p camelmailer -- version
CAMELMAILER_CONFIG_FILE_PATH=config/camelmailer.yml cargo run -p camelmailer -- initialize
CAMELMAILER_CONFIG_FILE_PATH=config/camelmailer.yml cargo run -p camelmailer -- make-admin-api-key ops
CAMELMAILER_CONFIG_FILE_PATH=config/camelmailer.yml cargo run -p camelmailer -- smtp-server
CAMELMAILER_CONFIG_FILE_PATH=config/camelmailer.yml cargo run -p camelmailer -- web-server
With postgres.enabled: true (or DATABASE_URL set) both servers run
against PostgreSQL; otherwise they fall back to non-persistent in-memory
storage and log a warning.
Configuration is one YAML file — config/camelmailer.example.yml
documents every group. For migrations, Postal-compatible config files load
unchanged (postal: group alias, POSTAL_CONFIG_FILE_PATH).
Store trait and
accepted messages through the MessageSink trait (camelmailer-core).
MemoryStore/MemorySink back the tests; the PostgreSQL implementations
in camelmailer-db implement the same traits, so protocol code never
touches persistence details.smtp_server.tls_enabled: true the server
loads the configured certificate/key, advertises STARTTLS (and withholds
AUTH) on plaintext sessions, upgrades the socket via rustls on request and
continues the same session encrypted. Messages received after the upgrade
are marked received_with_ssl.FOR UPDATE SKIP LOCKED, enters the owning tenant's RLS context per
message (no BYPASSRLS role needed), checks the suppression list, sends via
configured relays or MX lookup, retries soft failures with exponential
backoff, records every attempt in deliveries, and fires webhooks
(MessageSent/MessageDelayed/MessageDeliveryFailed/MessageHeld/MessageBounced,
plus MessageLoaded/MessageLinkClicked from tracking and MessageComplained
from feedback-loop reports).
With a usable return-path domain, outgoing SMTP uses the server's return
path and carries an X-CamelMailer-MsgID token so returned DSNs can update
the original message.
Incoming route mail is POSTed to the route's HTTP endpoint
(routes.endpoint_url).smtp_tls_security_level = may): a
foreign MX's certificate is not issued for our benefit, so requiring a
webpki trust chain would fail against practically every real MX. If the
handshake fails, delivery falls back to a fresh plaintext connection rather
than stalling. smtp.openssl_verify_mode governs certificate verification
for configured relays only (a smarthost with a known identity): peer
verifies against the webpki roots, none accepts any certificate; smtps://
relays use implicit TLS with verification by default. Deliveries over TLS
are recorded with sent_with_ssl.dns.dkim_identifier selector; the stored copy stays unsigned./track/c/<token> redirects and an open pixel is injected; the public,
unauthenticated tracking endpoints resolve tokens and record clicks/opens
into the RLS-protected tables.X-CamelMailer-Signature), and every
attempt is written to a tenant-scoped audit log.Camelmailer is API-first — the whole platform is drivable over HTTP so a
frontend (e.g. React) can be built on top. There are two token scopes, both
returning the native { status, time, data | error } envelope with
snake_case fields and { page, per_page, total, total_pages } pagination:
X-Admin-API-Key → /api/v2/admin/.... Org/server
management: organizations, servers (create/update/suspend, full config —
open/click tracking, spam thresholds, hook URLs, inbound domain, colour,
IP pool, default stream), domains, credentials, routes, webhooks,
suppressions, users, IP pools, and admin-API-key management.X-Server-API-Key → /api/v2/server/.... A per-server
token (a credentials record of type API) implies exactly one server;
every request is scoped to it and message-data queries enter that server's
RLS tenant context. It is a sibling router — not under admin auth.Server API surface:
| Area | Endpoints |
|---|---|
| Self / probe | GET /, GET /ping |
| Send | POST /messages, POST /messages/batch — builds MIME, authorises the From-domain, fans out one stored message per recipient; DKIM + tracking are applied by the worker at delivery time |
| Send with template | POST /messages/with_template, POST /messages/with_template/batch |
| Messages | GET /messages (filter: scope/status/tag/stream/query, paged), GET /messages/{id} (+ deliveries), /deliveries, /opens, /clicks, /raw (base64; 404 in privacy mode) |
| Statistics | GET /stats (status + open/click counters, ?from/&to), GET /stats/deliveries (outbound queue depth) |
| Bounces | GET /bounces, GET /bounces/{id} |
| Message streams | GET/POST /streams, GET/PATCH /streams/{permalink}, POST /streams/{permalink}/archive |
| Inbound | GET /inbound, GET /inbound/{id}, POST /inbound/{id}/bypass, POST /inbound/{id}/retry |
| Templates | GET/POST /templates, GET/PATCH /templates/{permalink}, POST /templates/{permalink}/archive, POST /templates/{permalink}/render (dry-run) |
Templates render with a small Mustache subset (camelmailer-core::template):
{{ var }} (HTML-escaped), {{{ var }}}/{{& var }} (raw), {{# s }}/{{^ s }}
sections over arrays/objects, dotted paths and .. No partials, lambdas, or
IO; output size and section-nesting depth are capped because the model is
untrusted end-user data. Tenant isolation is enforced everywhere — a token
for server A can never read or mutate server B's data — and is covered by
both router tests and Postgres RLS tests.
Everything above ships today, including the web dashboard. Deliberately deferred (documented so integrators know what not to expect yet):
dns.dkim_identifier selector.Questions, deployment help and what we are building next happen on Discord. Bug reports and feature requests belong in issues, where they stay searchable.
MIT, see LICENSE. Camelmailer began as a Rust rewrite of Postal, which is MIT too. The attribution for the portions that derive from it is in NOTICE.