Use Sendico SMTP mailbox for local notifications (#57)
This commit is contained in:
1 parent
3e4e496037
commit
b36eb72c23
18 files changed
+528
-36
No files matched your search
@@ -5,7 +5,7 @@ RUN apk add --no-cache protobuf protobuf-dev make
|
||||
WORKDIR /src
|
||||
COPY Makefile ./
|
||||
COPY backend ./backend
|
||||
RUN make generate-notifications
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod make generate-notifications
|
||||
ARG MODULE
|
||||
WORKDIR /src/backend/${MODULE}
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg/mod CGO_ENABLED=0 go build -trimpath -o /out/service .
|
||||
|
||||
@@ -18,10 +18,14 @@ messaging:
|
||||
reconnect_wait: 2
|
||||
templates: "/etc/sxchnge/templates.json"
|
||||
email:
|
||||
driver: resend
|
||||
driver: smtp
|
||||
settings:
|
||||
api_key_env: EMAIL_API_KEY
|
||||
sender_address: "" # Set your verified Resend sender here, then run make build.
|
||||
host: mail.sendico.io
|
||||
port: 465
|
||||
username: no-reply@sendico.io
|
||||
password_env: SMTP_PASSWORD
|
||||
network_timeout: 10
|
||||
sender_address: no-reply@sendico.io
|
||||
sender_name: "SXCHNGE"
|
||||
# Use replicas: 3 and environment: production for a three-node NATS cluster.
|
||||
# mode: ops validates resources without modifying them; app provisions them.
|
||||
|
||||
@@ -61,7 +61,7 @@ provision_agents() {
|
||||
mkdir -p "$directory"; chmod 755 "$directory"
|
||||
case "$service" in
|
||||
bff) paths='services/bff/jwt';;
|
||||
notifications) paths='infrastructure/nats/auth services/notifications/email/resend';;
|
||||
notifications) paths='infrastructure/nats/auth services/notifications/email/smtp';;
|
||||
nats) paths=infrastructure/nats/auth;;
|
||||
sendico) paths=services/bff/sendico;;
|
||||
esac
|
||||
@@ -105,13 +105,12 @@ up() {
|
||||
random | jq -Rs --arg field "$field" '{($field):rtrimstr("\n")}' | put "$path"
|
||||
fi
|
||||
done
|
||||
printf '{"api_key":""}' | ensure services/notifications/email/resend
|
||||
provision_mongo
|
||||
provision_agents
|
||||
compose up -d --wait --wait-timeout 120 nats
|
||||
retire_secrets
|
||||
normalize_email_secret
|
||||
kv services/notifications/email/resend | email_valid || fail 'Vault kv/services/notifications/email/resend needs api_key. Run make email; configure the verified sender in infra/local/notifications/runtime.yml, then make build && make up.'
|
||||
kv services/notifications/email/smtp | smtp_valid || fail 'Vault kv/services/notifications/email/smtp needs the mailbox password. Run make email.'
|
||||
compose up -d --wait --wait-timeout 180
|
||||
export_ca
|
||||
cat <<'MESSAGE'
|
||||
@@ -134,7 +133,7 @@ MESSAGE
|
||||
}
|
||||
email() {
|
||||
existing
|
||||
printf 'Resend API key (hidden): ' >&2
|
||||
printf 'SMTP mailbox password (hidden): ' >&2
|
||||
tty_state=$(stty -g)
|
||||
trap 'stty "$tty_state"; cleanup' EXIT
|
||||
stty -echo
|
||||
@@ -143,7 +142,8 @@ email() {
|
||||
printf '\n' >&2
|
||||
[ -n "$key" ] || fail 'A key is required'
|
||||
printf '%s' "$key" > "$WORK/email-key"
|
||||
normalize_email_secret
|
||||
jq -n --rawfile key "$WORK/email-key" '{api_key:$key}' | put services/notifications/email/resend
|
||||
echo 'API key saved in Vault. Set the verified sender in infra/local/notifications/runtime.yml, then make build && make up.'
|
||||
jq -n --rawfile password "$WORK/email-key" '{password:$password}' > "$WORK/smtp-secret"
|
||||
smtp_valid < "$WORK/smtp-secret" || fail 'Invalid SMTP mailbox password'
|
||||
put services/notifications/email/smtp < "$WORK/smtp-secret"
|
||||
echo 'SMTP password saved in Vault. The notifications Agent reloads it automatically.'
|
||||
}
|
||||
@@ -64,6 +64,7 @@ ensure() (
|
||||
mongo() { compose exec -T mongo mongosh --quiet --file /dev/stdin > "$WORK/mongo-result" 2> "$WORK/mongo-error" || return 1; cat "$WORK/mongo-result"; }
|
||||
admin_auth() { kv infrastructure/mongo/bootstrap | jq -r '"const admin=db.getSiblingDB(\"admin\"); admin.auth(\"local-admin\","+(.password|tojson)+");"'; }
|
||||
email_valid() { jq -e '(.api_key|type=="string") and (.api_key|test("\\S"))' >/dev/null; }
|
||||
smtp_valid() { jq -e 'keys==["password"] and (.password|type=="string" and length>0 and (explode|all(.>0 and .!=10 and .!=13)))' >/dev/null; }
|
||||
export_ca() {
|
||||
compose exec -T frontend cat /data/caddy/pki/authorities/local/root.crt | write "$STATE/ca.pem" 644
|
||||
}
|
||||
@@ -29,6 +29,10 @@ if images >/dev/null 2>&1; then fail 'Offline resolution succeeded'; fi
|
||||
[ "$(cat "$STATE/images.env")" = 'previous build' ] || fail 'Failed resolution replaced pins'
|
||||
if printf '{"api_key":"","sender_address":"notifications@localhost.test"}' | email_valid; then fail 'Empty email accepted'; fi
|
||||
printf '{"api_key":"fixture-only","sender_address":"sender@example.test"}' | email_valid
|
||||
printf '{"password":"fixture-only"}' | smtp_valid
|
||||
for invalid in '{"password":""}' '{"password":"bad\nvalue"}' '{"password":"bad\u0000value"}' '{"password":"fixture","username":"not-a-secret"}'; do
|
||||
if printf '%s' "$invalid" | smtp_valid; then fail 'Invalid SMTP secret accepted'; fi
|
||||
done
|
||||
# Initialized Vault without matching local keys must fail without initializing it again.
|
||||
vault() { printf '{"initialized":true}'; }
|
||||
if (initialize) > "$WORK/initialize-error" 2>&1; then fail 'Missing local keys accepted'; fi
|
||||
|
||||
@@ -61,11 +61,11 @@ layout() {
|
||||
if [ -f "$STATE/sendico/settings.env" ]; then
|
||||
kv services/bff/sendico | jq -e 'keys==["client_secret","login","password"]' >/dev/null || fail 'Sendico secret contains non-secret configuration'
|
||||
fi
|
||||
kv services/notifications/email/resend | jq -e 'keys==["api_key"]' >/dev/null || fail 'Non-secret email configuration remains in Vault'
|
||||
kv services/notifications/email/smtp | jq -e 'keys==["password"]' >/dev/null || fail 'SMTP secret contains non-secret configuration'
|
||||
for old in bootstrap/mongo bff caller services/notifications/callers/bff email nats tls frontend-tls notifications-tls; do
|
||||
[ "$(vault "kv/metadata/$old" GET yes)" = null ] || fail "Obsolete KV entry remains: $old"
|
||||
done
|
||||
for path in services/bff/jwt services/notifications/email/resend infrastructure/nats/auth infrastructure/mongo/bootstrap; do
|
||||
for path in services/bff/jwt services/notifications/email/smtp infrastructure/nats/auth infrastructure/mongo/bootstrap; do
|
||||
vault "kv/metadata/$path" | jq -e '.data.current_version>0' >/dev/null || fail "Missing canonical KV entry: $path"
|
||||
done
|
||||
echo 'PASS: canonical Vault hierarchy present; obsolete flat and TLS entries absent'
|
||||
|
||||
@@ -22,8 +22,8 @@ env_template "SXCHNGE_NATS_URL" {
|
||||
|
||||
|
||||
|
||||
env_template "EMAIL_API_KEY" {
|
||||
contents = "{{ with secret \"kv/data/services/notifications/email/resend\" }}{{ .Data.data.api_key }}{{ end }}"
|
||||
env_template "SMTP_PASSWORD" {
|
||||
contents = "{{ with secret \"kv/data/services/notifications/email/smtp\" }}{{ .Data.data.password }}{{ end }}"
|
||||
error_on_missing_key = true
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user