Skip to content

Commit 006235c

Browse files
ralyodioclaude
andauthored
Feat/mail all members (#55)
* feat(mail): give every verified member a free @bbs.profullstack.com mailbox Email was built but paid-only (Founding Lifetime gate) and never wired to a running backend. Make it a free benefit of membership and split the address domain from the mail-server host. - internal/mailu: Mailu admin-API client; EnsureUser idempotently provisions a mailbox via the loopback admin REST API (token = mailu.env API_TOKEN). - main.go: auto-provision <name>@<mailDomain> at join@ verification and on first Mail open; un-gate the Mail hub entry + mail@ (membership/email-verified, not Premium); address domain (AGENTBBS_MAIL_ADDR_DOMAIN, default the BBS host) is now distinct from the mail server host (AGENTBBS_MAIL_DOMAIN) and the webmail URL. Drop the forwardemail alias path (Mailu now owns delivery for everyone). - mailbox: gate on membership (a registered handle) instead of Paid; ErrNotPaid -> ErrNotMember. - join@ copy: list email under free membership; premium now pitches custom domains + Tor only. - setup.sh / docs/mail.md / deploy/mailu: address-domain vs server-host split, Mailu API token, MX for the address domain, local-relay SMTP for verify codes. Co-Authored-By: Claude Opus 4.8 <[email protected]> * chore(mailu): pin Docker network subnet to match SUBNET; ignore runtime state The base compose declares no network, so Docker assigns the default bridge an arbitrary subnet that won't match mailu.env SUBNET — breaking Mailu's internal service auth/relay. Add a docker-compose.override.yml.example that pins the default network to 192.168.203.0/24, and gitignore the live override + Mailu runtime state (mailu.env, certs/, data/). Co-Authored-By: Claude Opus 4.8 <[email protected]> * feat(mail): plaintext loopback IMAP so the gateway bypasses Mailu's front Mailu's front (nginx mail proxy) pre-authenticates against Mailu's user DB before proxying to Dovecot, which rejects the Dovecot master-user login <addr>*gateway. The gateway must reach Dovecot directly. The imap container has no TLS cert (only the front does), so the bypass is plaintext over loopback — the master password never leaves the host. - mailbox: IMAPConfig.Plaintext dials with DialInsecure (loopback only). - main.go: mailClientFor sets Plaintext from AGENTBBS_MAIL_IMAP_PLAINTEXT. - override.example: add the unbound resolver (admin needs DNSSEC), webmail image fix (2024.06 uses mailu/webmail), and publish Dovecot 143 on 127.0.0.1:14143. - docs/mail.md: document the front-bypass, the dovecot.conf master passdb (Mailu includes that exact filename), and the 644 master-users perms (640 = temp_fail). Co-Authored-By: Claude Opus 4.8 <[email protected]> * deploy(mailu): wire gateway IMAP to the loopback Dovecot path in setup.sh setup.sh §9e set AGENTBBS_MAIL_IMAP_ADDR to the front's :993, which the front's auth proxy rejects for the master-user login (and would clobber the working loopback wiring on every self-update). Point it at 127.0.0.1:14143 + AGENTBBS_MAIL_IMAP_PLAINTEXT=1 instead, matching the override + docs. Co-Authored-By: Claude Opus 4.8 <[email protected]> * feat(mail): give free members a webmail password at join@ The gateway opens mailboxes via the Dovecot master user (no member password), but webmail (Roundcube) needs the member to have a password. join@ now sets a fresh, readable webmail password via the Mailu API and shows it with the webmail URL + login, so free members can use webmail at mail.profullstack.com. - mailu: SetPassword (PATCH /user/<email> raw_password) + test. - main.go: setWebmailPassword + readablePassword; join@ displays url/login/password. Co-Authored-By: Claude Opus 4.8 <[email protected]> --------- Co-authored-by: Claude Opus 4.8 <[email protected]>
1 parent de5517c commit 006235c

13 files changed

Lines changed: 725 additions & 164 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,11 @@
66
*.log
77
.env
88

9+
# Mailu runtime: secrets, local network override, and state
10+
deploy/mailu/mailu.env
11+
deploy/mailu/docker-compose.override.yml
12+
deploy/mailu/data/
13+
deploy/mailu/certs/
14+
915
# Claude Code local worktrees/state
1016
.claude/

cmd/agentbbs/main.go

Lines changed: 171 additions & 73 deletions
Large diffs are not rendered by default.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# docker-compose.override.yml — copy to docker-compose.override.yml (gitignored).
2+
# Compose loads this file automatically. It carries three fixes the trimmed base
3+
# compose needs; see docs/mail.md for the full rationale.
4+
networks:
5+
default:
6+
driver: bridge
7+
ipam:
8+
config:
9+
# Mailu trusts SUBNET (mailu.env) as its internal network for
10+
# service-to-service auth/relay; the real network MUST match it.
11+
- subnet: 192.168.203.0/24
12+
services:
13+
# Mailu requires a DNSSEC-validating resolver or admin won't start.
14+
resolver:
15+
image: ghcr.io/mailu/unbound:2024.06
16+
env_file: mailu.env
17+
restart: always
18+
networks:
19+
default:
20+
ipv4_address: 192.168.203.254
21+
front: { dns: [192.168.203.254], depends_on: [resolver] }
22+
admin: { dns: [192.168.203.254], depends_on: [resolver] }
23+
imap:
24+
dns: [192.168.203.254]
25+
depends_on: [resolver]
26+
# Publish Dovecot directly on loopback so the agentbbs gateway can use the
27+
# master-user login (the front's nginx auth proxy rejects "<addr>*master").
28+
# Plaintext is fine: the connection never leaves the host.
29+
ports: ["127.0.0.1:14143:143"]
30+
smtp: { dns: [192.168.203.254], depends_on: [resolver] }
31+
antispam: { dns: [192.168.203.254], depends_on: [resolver] }
32+
# In Mailu 2024.06 the webmail image is "webmail" (not "roundcube:2024.06").
33+
webmail:
34+
image: ghcr.io/mailu/webmail:2024.06
35+
dns: [192.168.203.254]
36+
depends_on: [resolver]

deploy/mailu/mailu.env.example

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1-
# Mailu configuration for mail.profullstack.com — copy to deploy/mailu/mailu.env
2-
# and fill the secrets. See docs/mail.md for the full setup (DNS, certs, gateway).
1+
# Mailu configuration — copy to deploy/mailu/mailu.env and fill the secrets.
2+
# See docs/mail.md for the full setup (DNS, certs, gateway).
33
#
44
# Generate secrets with: openssl rand -hex 16
5+
#
6+
# NOTE: DOMAIN is the member ADDRESS domain (the @-part); HOSTNAMES is the mail
7+
# SERVER host (TLS/HELO + webmail/admin/API). These deliberately differ:
8+
# members get <name>@bbs.profullstack.com, served from mail.profullstack.com.
59

610
# --- General -----------------------------------------------------------------
711
SECRET_KEY=CHANGEME_16_HEX # openssl rand -hex 16
8-
DOMAIN=mail.profullstack.com # member addresses are <name>@mail.profullstack.com
12+
DOMAIN=bbs.profullstack.com # member addresses are <name>@bbs.profullstack.com
913
HOSTNAMES=mail.profullstack.com,smtp.profullstack.com
1014
POSTMASTER=postmaster
1115
# Apex profullstack.com is reserved for corporate mail and is NOT served here.
1216

17+
# Admin REST API: agentbbs auto-provisions member mailboxes through it. Mirror
18+
# this value into the agentbbs service as AGENTBBS_MAIL_API_TOKEN.
19+
API=true
20+
API_TOKEN=CHANGEME_api_token # openssl rand -hex 24
21+
1322
# TLS_FLAVOR=mail: Mailu does NOT run its own ACME (Caddy owns :80/:443). We feed
1423
# it certs copied from Caddy's mail.profullstack.com cert (deploy/mailu/refresh-certs.sh).
1524
TLS_FLAVOR=mail
@@ -32,13 +41,16 @@ MESSAGE_SIZE_LIMIT=52428800 # 50 MB
3241
# A Dovecot master user lets the agentbbs gateway open any member's mailbox with
3342
# one secret (login "<name>*<master>"). Created by deploy/mailu/provision-mailbox.sh.
3443
# Mirror these into the agentbbs service env:
44+
# AGENTBBS_MAIL_ADDR_DOMAIN=bbs.profullstack.com
3545
# AGENTBBS_MAIL_DOMAIN=mail.profullstack.com
3646
# AGENTBBS_MAIL_IMAP_ADDR=mail.profullstack.com:993
3747
# AGENTBBS_MAIL_SMTP_ADDR=127.0.0.1:25
48+
# AGENTBBS_MAIL_ADMIN_URL=http://127.0.0.1:8080
49+
# AGENTBBS_MAIL_API_TOKEN=<the API_TOKEN above>
3850
# AGENTBBS_MAIL_MASTER_USER=gateway
3951
# AGENTBBS_MAIL_MASTER_PASS=<the master password you set>
4052

4153
# --- Admin bootstrap ---------------------------------------------------------
4254
INITIAL_ADMIN_ACCOUNT=admin
43-
INITIAL_ADMIN_DOMAIN=mail.profullstack.com
55+
INITIAL_ADMIN_DOMAIN=bbs.profullstack.com
4456
INITIAL_ADMIN_PW=CHANGEME_admin_password

deploy/mailu/provision-mailbox.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
set -euo pipefail
1515

1616
MAILU_DIR="${MAILU_DIR:-/opt/agentbbs/deploy/mailu}"
17-
DOMAIN="${MAIL_DOMAIN:-mail.profullstack.com}"
17+
# The address domain (the @-part), which may differ from the mail server host.
18+
DOMAIN="${MAIL_ADDR_DOMAIN:-${MAIL_DOMAIN:-bbs.profullstack.com}}"
1819
MASTER_USER="${AGENTBBS_MAIL_MASTER_USER:-gateway}"
1920
QUOTA_BYTES="${MAIL_QUOTA_BYTES:-1000000000}" # 1 GB
2021

docs/mail.md

Lines changed: 97 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
# Mail — self-hosted Mailu at `mail.profullstack.com`
1+
# Mail — self-hosted Mailu
22

3-
AgentBBS gives **Founding Lifetime (paid) members** a real mailbox at
4-
`<name>@mail.profullstack.com`, reached two ways:
3+
AgentBBS gives **every verified member** (free and paid alike) a real mailbox at
4+
`<name>@bbs.profullstack.com`, reached two ways:
55

6-
- **Webmail**`https://mail.profullstack.com` (Roundcube), the only
7-
member-facing mail surface.
6+
- **Webmail**`https://mail.profullstack.com` (Roundcube).
87
- **AgentMail** — the in-BBS client (`internal/mailbox`): the `Mail` hub entry
98
or `ssh [email protected]` (a TUI for humans, a JSON bot mode for
109
agents). It connects to this stack.
1110

11+
Two distinct names are involved — don't conflate them:
12+
13+
| | value | role |
14+
|---|---|---|
15+
| **Address domain** | `bbs.profullstack.com` | the `@`-part of member addresses (`AGENTBBS_MAIL_ADDR_DOMAIN`) |
16+
| **Mail server host** | `mail.profullstack.com` | where IMAP/SMTP/webmail actually run (`AGENTBBS_MAIL_DOMAIN`) |
17+
1218
The apex `profullstack.com` is **reserved for corporate mail** and is not served
13-
here — member mail lives only on the `mail.` subdomain.
19+
here.
1420

1521
## Architecture
1622

@@ -19,97 +25,139 @@ Mailu (Postfix + Dovecot + Roundcube + rspamd) runs as a Docker Compose stack:
1925

2026
- Mailu owns the **mail ports** on the host: `25, 465, 587, 993, 995`.
2127
- Mailu's HTTP front is bound to **loopback** (`127.0.0.1:8080`); **Caddy**
22-
reverse-proxies `https://mail.profullstack.com` to it (webmail + admin).
28+
reverse-proxies `https://mail.profullstack.com` to it (webmail + admin + API).
2329
- **TLS:** `TLS_FLAVOR=mail` — Mailu does *not* run its own ACME (Caddy is the
24-
only ACME client). Caddy obtains the `mail.profullstack.com` cert from its site
25-
block; [`deploy/mailu/refresh-certs.sh`](../deploy/mailu/refresh-certs.sh)
26-
copies it into Mailu and reloads it on renewal — the same pattern as the
27-
Ergo/IRC and NNTP cert refreshers.
30+
only ACME client). Caddy obtains the `mail.profullstack.com` cert; the cert
31+
refresher copies it into Mailu and reloads on renewal.
2832
- The **agentbbs gateway** reads/sends on behalf of members: IMAP via a Dovecot
2933
**master user** (one secret opens any mailbox), SMTP via the co-located relay
3034
on `127.0.0.1:25`. Members therefore never manage an IMAP/SMTP password.
35+
- **Provisioning** is automatic: when a member verifies their email at `join@`
36+
(or opens `Mail`), agentbbs ensures `<name>@bbs.profullstack.com` exists via
37+
Mailu's **admin REST API** (`internal/mailu`, token = `API_TOKEN`). The manual
38+
`deploy/mailu/provision-mailbox.sh` is only for the gateway master user and
39+
backfills.
3140

3241
```
3342
┌─────────── Caddy (:443) ───────────┐
34-
webmail → │ mail.profullstack.com → 127.0.0.1:8080 (Mailu front, HTTP)
43+
webmail → │ mail.profullstack.com → 127.0.0.1:8080 (Mailu front: webmail/admin/API)
3544
└───────────────┬─────────────────────┘
3645
│ copies LE cert (refresh-certs.sh)
3746
clients → Mailu front (:25 :465 :587 :993 :995) ──→ Postfix / Dovecot / rspamd
3847
3948
agentbbs ──IMAP 993 (master user)──┘ ──SMTP 127.0.0.1:25 (local relay)──▶
49+
agentbbs ──admin API (token) http://127.0.0.1:8080/api/v1──▶ (auto-provision)
4050
```
4151

4252
## DNS
4353

44-
`mail.profullstack.com` and `smtp.profullstack.com` A records are added. Also set:
54+
Mail is delivered to the **address domain** (`bbs.profullstack.com`), so its MX
55+
must point at the **server host** (`mail.profullstack.com`):
4556

4657
| Type | Host | Value |
4758
|---|---|---|
4859
| A | `mail.profullstack.com` | host IP |
49-
| A | `smtp.profullstack.com` | host IP |
50-
| MX | `mail.profullstack.com` | `10 mail.profullstack.com.` |
51-
| TXT (SPF) | `mail.profullstack.com` | `v=spf1 mx -all` |
52-
| TXT (DMARC) | `_dmarc.mail.profullstack.com` | `v=DMARC1; p=quarantine; rua=mailto:[email protected]` |
53-
| TXT (DKIM) | `dkim._domainkey.mail.profullstack.com` | from `flask mailu config-export` after first boot |
60+
| MX | `bbs.profullstack.com` | `10 mail.profullstack.com.` |
61+
| TXT (SPF) | `bbs.profullstack.com` | `v=spf1 mx -all` |
62+
| TXT (DMARC) | `_dmarc.bbs.profullstack.com` | `v=DMARC1; p=quarantine; rua=mailto:[email protected]` |
63+
| TXT (DKIM) | `dkim._domainkey.bbs.profullstack.com` | from `flask mailu config-export` after first boot |
5464
| PTR | host IP | `mail.profullstack.com` (set at your VPS provider) |
5565

56-
> **Port 25 / deliverability:** many cloud providers block outbound `:25` by
57-
> default — request an unblock, set the PTR/rDNS, and warm the IP, or relay
58-
> outbound through a smarthost. Inbound MX and the gateway's local submission
59-
> work regardless.
66+
> **Port 25 / deliverability:** many cloud providers (incl. DigitalOcean) block
67+
> outbound `:25` by default — request an unblock, set the PTR/rDNS, and warm the
68+
> IP, or relay outbound through a smarthost. Inbound MX and the gateway's local
69+
> submission work regardless.
6070
6171
## Install
6272

6373
```bash
6474
cd /opt/agentbbs/deploy/mailu
65-
cp mailu.env.example mailu.env # fill SECRET_KEY, INITIAL_ADMIN_PW, etc.
75+
cp mailu.env.example mailu.env # fill SECRET_KEY, INITIAL_ADMIN_PW, API_TOKEN, DOMAIN=bbs.profullstack.com, HOSTNAMES=mail.profullstack.com
6676
docker compose up -d
67-
# seed the gateway master user + (optionally) backfill member mailboxes:
77+
# add the address domain + the gateway master user:
78+
docker compose exec admin flask mailu domain bbs.profullstack.com
6879
AGENTBBS_MAIL_MASTER_USER=gateway ./provision-mailbox.sh --master "$(openssl rand -hex 16)"
6980
```
7081

71-
Add the Caddy site (setup.sh writes this when `MAIL=1`):
72-
73-
```
74-
mail.profullstack.com {
75-
encode zstd gzip
76-
reverse_proxy 127.0.0.1:8080
77-
}
78-
```
79-
80-
Then install the cert refresher on a timer (setup.sh does this too):
81-
82-
```bash
83-
install -m 0755 deploy/mailu/refresh-certs.sh /usr/local/bin/agentbbs-mailu-certs
84-
# systemd timer runs it every ~12h; first run swaps in the real cert once Caddy issues it.
85-
```
82+
setup.sh writes the Caddy `mail.profullstack.com` site and the cert-refresh
83+
timer when `MAIL=1`, and brings the stack up once `mailu.env` exists.
8684

8785
## agentbbs gateway env
8886

89-
Set these on the agentbbs service so the `Mail` hub entry / `ssh mail@` work:
87+
Set these on the agentbbs service (setup.sh §9e upserts the non-secret ones):
9088

9189
| Var | Value |
9290
|---|---|
91+
| `AGENTBBS_MAIL_ADDR_DOMAIN` | `bbs.profullstack.com` |
9392
| `AGENTBBS_MAIL_DOMAIN` | `mail.profullstack.com` |
94-
| `AGENTBBS_MAIL_IMAP_ADDR` | `mail.profullstack.com:993` |
93+
| `AGENTBBS_MAIL_IMAP_ADDR` | `127.0.0.1:14143` (Dovecot direct, loopback) |
94+
| `AGENTBBS_MAIL_IMAP_PLAINTEXT` | `1` (the loopback path is plaintext) |
9595
| `AGENTBBS_MAIL_SMTP_ADDR` | `127.0.0.1:25` |
96+
| `AGENTBBS_MAIL_ADMIN_URL` | `http://127.0.0.1:8080` |
97+
| `AGENTBBS_MAIL_API_TOKEN` | the Mailu `API_TOKEN` (secret) |
9698
| `AGENTBBS_MAIL_MASTER_USER` | `gateway` |
97-
| `AGENTBBS_MAIL_MASTER_PASS` | the master password set above |
99+
| `AGENTBBS_MAIL_MASTER_PASS` | the master password set above (secret) |
100+
| `AGENTBBS_WEBMAIL_URL` | `https://mail.profullstack.com` (default = mail host) |
101+
102+
Without `AGENTBBS_MAIL_API_TOKEN` auto-provisioning is skipped (the address is
103+
still shown); without `AGENTBBS_MAIL_MASTER_PASS` the gateway can't open
104+
mailboxes.
105+
106+
### Why the gateway talks to Dovecot directly (plaintext loopback)
107+
108+
Mailu's **front** (nginx mail proxy) pre-authenticates every IMAP/SMTP login
109+
against Mailu's user DB before proxying to Dovecot — and it rejects the Dovecot
110+
master-user login form `<addr>*gateway`. So the gateway must reach **Dovecot
111+
directly**, bypassing the front. The `imap` container has no TLS cert (only the
112+
front does), so the bypass is plaintext over loopback — safe because the
113+
connection (and the master password) never leave the host. Wiring:
114+
115+
- Publish Dovecot's IMAP on loopback (docker-compose.override.yml):
116+
`imap.ports: ["127.0.0.1:14143:143"]`.
117+
- The Dovecot master user is defined in `data/overrides/dovecot/dovecot.conf`
118+
(Mailu includes exactly that filename — *not* `*.conf`):
119+
120+
```
121+
auth_master_user_separator = *
122+
passdb { driver = passwd-file; master = yes; args = /overrides/master-users }
123+
```
124+
125+
with `data/overrides/dovecot/master-users` holding `gateway:{SHA512-CRYPT}$6$…`
126+
(the hash of `AGENTBBS_MAIL_MASTER_PASS`). The file must be **world-readable
127+
(644)** — Dovecot reads it as a non-root user, and 640 root:root yields a
128+
`temp_fail`. Do **not** add `result_success = continue` (that would also
129+
require the target user's own password); the target mailbox comes from userdb.
130+
- Point the gateway at it: `AGENTBBS_MAIL_IMAP_ADDR=127.0.0.1:14143` +
131+
`AGENTBBS_MAIL_IMAP_PLAINTEXT=1`.
132+
133+
## Sending mail from the BBS (verify codes + notifications)
134+
135+
The join@ verification code and signup notifications use `internal/mail` (the
136+
`AGENTBBS_SMTP_*` knobs), separate from the per-member mailbox client. Point
137+
them at the local Mailu relay so codes actually send:
138+
139+
```
140+
AGENTBBS_SMTP_HOST=127.0.0.1
141+
AGENTBBS_SMTP_PORT=25
142+
143+
# user/pass omitted: the co-located relay accepts local submission unauthenticated
144+
```
98145

99146
## Provisioning member mailboxes
100147

101-
A mailbox must exist before the gateway can open it. Provision when a member
102-
becomes paid (or backfill):
148+
Provisioning is automatic at `join@` verification. To create or backfill by hand:
103149

104150
```bash
105-
deploy/mailu/provision-mailbox.sh alice # creates alice@mail.profullstack.com
151+
deploy/mailu/provision-mailbox.sh alice # creates alice@bbs.profullstack.com
106152
```
107153

108-
The Dovecot **master user** (`gateway`) then authenticates as any member with
109-
the login form `alice*gateway` + the master password — which is exactly what
154+
(Set `MAIL_DOMAIN=bbs.profullstack.com` for the script, since the address domain
155+
differs from the server host.)
156+
157+
The Dovecot **master user** (`gateway`) authenticates as any member with the
158+
login form `alice*gateway` + the master password — exactly what
110159
`internal/mailbox`'s IMAP adapter sends. See
111-
[`deploy/mailu/README.md`](../deploy/mailu/README.md) for the master-user
112-
override and operational details.
160+
[`deploy/mailu/README.md`](../deploy/mailu/README.md) for details.
113161

114162
## Webmail only for members
115163

internal/mailbox/client.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ import (
77
"strings"
88
)
99

10-
// Identity is the acting member and whether they hold the paid membership.
10+
// Identity is the acting member. AgentMail is a free benefit of membership, so
11+
// having a registered handle is the only requirement; Paid is retained for
12+
// tier-aware features (e.g. quotas) but no longer gates access.
1113
type Identity struct {
1214
Name string // local-part / handle, e.g. "alice"
13-
Paid bool // Founding Lifetime Member; mail is gated on this
15+
Paid bool // Founding Lifetime Member (informational; does not gate mail)
1416
}
1517

16-
// ErrNotPaid is returned to a non-paid member attempting a mail action.
17-
var ErrNotPaid = errors.New("AgentMail is a Founding Lifetime Member feature ($99 one-time) — upgrade: ssh [email protected]")
18+
// ErrNotMember is returned when a caller without a registered handle attempts a
19+
// mail action. AgentMail is open to every verified member.
20+
var ErrNotMember = errors.New("AgentMail is a member feature — register first: ssh [email protected]")
1821

19-
// Client is the ergonomic, paid-gated facade the TUI and bot mode use. Every
22+
// Client is the ergonomic, member-gated facade the TUI and bot mode use. Every
2023
// method returns plain structs, so the same calls serve humans and agents.
2124
type Client struct {
2225
t Transport
@@ -25,21 +28,21 @@ type Client struct {
2528
pageSize int
2629
}
2730

28-
// NewClient builds a paid-gated client. domain is the mail domain (e.g.
29-
// mail.profullstack.com); pageSize defaults to 50 when <= 0.
31+
// NewClient builds a member-gated client. domain is the email address domain
32+
// (e.g. bbs.profullstack.com); pageSize defaults to 50 when <= 0.
3033
func NewClient(t Transport, id Identity, domain string, pageSize int) *Client {
3134
if pageSize <= 0 {
3235
pageSize = 50
3336
}
3437
return &Client{t: t, id: id, domain: domain, pageSize: pageSize}
3538
}
3639

37-
// Address is the member's own mailbox address, e.g. alice@mail.profullstack.com.
40+
// Address is the member's own mailbox address, e.g. alice@bbs.profullstack.com.
3841
func (c *Client) Address() string { return c.id.Name + "@" + c.domain }
3942

4043
func (c *Client) gate() error {
41-
if c.id.Name == "" || !c.id.Paid {
42-
return ErrNotPaid
44+
if c.id.Name == "" {
45+
return ErrNotMember
4346
}
4447
return nil
4548
}

internal/mailbox/imap.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ type IMAPConfig struct {
2424
// SMTPUser/SMTPPass default to Username/Password when empty.
2525
SMTPUser string
2626
SMTPPass string
27+
// Plaintext dials IMAP without TLS. Used only for a co-located backend over
28+
// loopback (the Mailu gateway hitting Dovecot directly on 127.0.0.1, bypassing
29+
// the front's auth proxy so master-user login works) — the password never
30+
// leaves the host. Never enable it for a remote server.
31+
Plaintext bool
2732
}
2833

2934
// imapTransport is a Transport backed by a single authenticated IMAP connection
@@ -38,7 +43,11 @@ type imapTransport struct {
3843

3944
// NewIMAPTransport dials the IMAP server, logs in, and returns a Transport.
4045
func NewIMAPTransport(cfg IMAPConfig) (Transport, error) {
41-
c, err := imapclient.DialTLS(cfg.IMAPAddr, nil)
46+
dial := imapclient.DialTLS
47+
if cfg.Plaintext {
48+
dial = imapclient.DialInsecure
49+
}
50+
c, err := dial(cfg.IMAPAddr, nil)
4251
if err != nil {
4352
return nil, fmt.Errorf("imap dial %s: %w", cfg.IMAPAddr, err)
4453
}

0 commit comments

Comments
 (0)