Repository navigation
feat(mailer): support a separate TCP relay address - #65
Conversation
|
All contributors have signed the CLA. ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
|
This repo's model isn't fully set up yet. OPENAI_COMPATIBLE_MODEL env var is required when the model is set to "openai-compatible/byok". set it to the model ID served by your OpenAI-compatible endpoint (e.g. a Cloudflare AI Gateway or DashScope model). see https://docs.pullfrog.com/openai-compatible for setup.
|
|
recheck |
|
@pullfrog please review this PR |
There was a problem hiding this comment.
ℹ️ No critical issues — two minor suggestions inline.
Reviewed changes
internal/mailerrelay dial target —SMTPgainsconnectHost/connectPort, defaulting to the configured host/port and overridable viaEMAIL_SMTP_CONNECT_HOST/EMAIL_SMTP_CONNECT_PORT.Senddials the override, while TLS SNI/certificate verification andsmtp.PlainAuthcontinue to uses.host(andnewDialersstill receivess.host).DEPLOY.md— adds a short section stating the relay must forward SMTP without terminating TLS.internal/mailer/smtp_relay_test.go— starts a TLS listener that rejects the handshake, points the connect override at it, and asserts both that the send fails and that the observed ClientHelloServerNameis the configured SMTP host (smtp.example.com). The test is meaningful: it would fail ifnewDialerswere switched tos.connectHost, or if the relay addr were not used.
The core change is correct: verification stays pinned to the configured host, so the relay cannot MITM, and the dial stays bounded by the existing deadline.
ℹ️ Nitpicks
- The new test exercises only the implicit-TLS branch (
implicitTLS=true). The STARTTLS branch pinsServerNamefroms.hostseparately (smtp.go:143), so it is not at risk here, but a second case through the STARTTLS path would lock the identity guarantee for that mode too. docs/ARCHITECTURE.md§12 already describes the dial path in detail (includingnewDialersand why the dial is bounded). A one-line mention of the relay override there would keep that section the authoritative description of mail transport.
opencode-go/deepseek-v4.1-flash | 𝕏
shockalotti
left a comment
There was a problem hiding this comment.
Thanks for this — the dial-vs-identity split is done right (dial connectHost:connectPort, TLS ServerName/auth stay on s.host, and the SNI test proves it). Requesting one structural change before merge:
Read the env at the config layer, not inside NewSMTP. os.Getenv inside the constructor hides behavior from SMTPConfig/BuildMailer — the single place this repo decides which email path sends (see CLAUDE.md email rule). Consequences as written: the same stored config dials different hosts depending on process env, invisible to the settings UI and the test-email path, and unconstructable per-instance in tests. Suggested shape: add connectHost/connectPort fields to SMTPConfig, populate them from EMAIL_SMTP_CONNECT_HOST/EMAIL_SMTP_CONNECT_PORT where the config is built (handler/boot), and pass them into NewSMTP explicitly (params or options), keeping NewSMTP pure.
Smaller ask: log at startup/send time when a relay override is active, so “which path sent this?” stays answerable.
Happy to re-review once the config threading is in.
|
Updated in 1a9fce3 and merged current main.
Frontend checks/build, Go vet/build/full tests, and focused relay race checks pass locally. Ready for another review. |
There was a problem hiding this comment.
ℹ️ The relay configuration now threads cleanly:
NewSMTPis pure, the override flows throughSMTPConfig/BuildMailer, and the boot + settings-reload integration test proves the TLSServerNamestays pinned to the configured SMTP host. One test-input nit inline.
Reviewed changes
- Reworked
NewSMTPto takeconnectHost/connectPortexplicitly and default empty values to the SMTP host/port in the constructor (no more env reads). - Parsed the override in
internal/config(SMTPConnectHost/SMTPConnectPort) and threaded it throughSMTPConfig, a newHandler.BuildMailer, and boot/settings-save. - Routed both boot mailer paths through
h.BuildMailer, which logs an active relay; documented the vars inDEPLOY.md§1 + troubleshooting,.env.example, andARCHITECTURE.md§12. - Added config-parsing,
NewSMTP-defaulting, implicit-TLS + STARTTLS identity, and server boot/reload integration tests.
opencode-go/deepseek-v4.1-flash | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Corrected the relay integration test's PATCH body to send
email_from— the keyPatchEmailSettingsactually decodes — instead of the silently-ignoredfrom_address, so the saved From is now populated rather than empty. This was the only open Pullfrog thread from the prior review; it is now addressed and resolved.
The preceding commit (1a9fce3) had already threaded the relay address through internal/config → SMTPConfig → BuildMailer, keeping NewSMTP pure, with TLS SNI/certificate verification and smtp.PlainAuth pinned to the configured SMTP host (smtp.go:111, smtp.go:123, smtp.go:144, smtp.go:153). This delta touches only the test assertion input and changes no production behavior.
opencode-go/deepseek-v4.1-flash | 𝕏

Some hosts block direct SMTP connections but provide a TCP relay. This change lets Calnode connect to that relay while keeping the configured SMTP host for TLS verification and authentication.
EMAIL_SMTP_CONNECT_HOSTandEMAIL_SMTP_CONNECT_PORTare optional connection overrides, parsed byinternal/configand passed explicitly throughSMTPConfig,BuildMailer, andNewSMTP. Each unset value uses the configured SMTP host or port. Overrides apply at startup with environment or database settings and remain active after settings reloads and during test emails. Active relays are logged. The relay must forward the connection without terminating TLS.Updated with current main and resolved the SMTP message-validation merge conflict.
Validation:
pnpm install --frozen-lockfile,pnpm check, andpnpm buildpass.go vet ./...,go build ./..., andgo test ./...pass.