diff --git a/.env.example b/.env.example index 3201243..696c27f 100644 --- a/.env.example +++ b/.env.example @@ -57,7 +57,8 @@ SMTP_HOST= SMTP_PORT=587 SMTP_USER= SMTP_PASSWORD= -SMTP_FROM="ANOUMA " +# Mailbox address used by the SMTP relay. The app displays ANOUMA as the name. +SMTP_FROM=support@anouma.org # Keep true for publicly trusted SMTP certificates. Set false only when the # configured SMTP proxy intentionally uses a private/self-signed certificate. SMTP_TLS_REJECT_UNAUTHORIZED=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27cae24..3fc5174 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,13 +55,7 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build and push image - uses: docker/build-push-action@v6 - with: - context: . - push: true - tags: ${{ env.IMAGE }}:${{ github.sha }} - cache-from: type=gha,scope=anouma-image - cache-to: type=gha,mode=max,scope=anouma-image + - name: Build image + run: docker build --tag "$IMAGE:$GITHUB_SHA" . + - name: Push image + run: docker push "$IMAGE:$GITHUB_SHA" diff --git a/lib/email/bookingTemplates.ts b/lib/email/bookingTemplates.ts index 545c2ef..cc94db7 100644 --- a/lib/email/bookingTemplates.ts +++ b/lib/email/bookingTemplates.ts @@ -70,7 +70,7 @@ export function bookingConfirmedEmail( extras.push(renderButton("Meeting betreten", args.online.joinUrl)); extras.push( `

Meeting-Passwort:

` + - `

${escapeHtml(args.online.password)}

`, + `

${escapeHtml(args.online.password)}

`, ); } if (args.onsite) { diff --git a/lib/email/layout.ts b/lib/email/layout.ts index f3259d0..61e7832 100644 --- a/lib/email/layout.ts +++ b/lib/email/layout.ts @@ -5,6 +5,8 @@ const colors = { creamLight: "#fbf6f1", }; +const emailFont = "'Antropos', Georgia, 'Times New Roman', serif"; + export function escapeHtml(value: string): string { return value .replace(/&/g, "&") @@ -17,7 +19,7 @@ export function renderButton(label: string, href: string): string { return ` @@ -28,7 +30,7 @@ export function renderButton(label: string, href: string): string { export function renderFactBox(rows: { label: string; value: string }[]): string { const cells = rows .map( - (row, i) => `

+ (row, i) => `

${escapeHtml(row.label)}:
${escapeHtml(row.value)}

`, ) @@ -45,24 +47,29 @@ export function renderEmailShell(args: { preheader?: string; bodyHtml: string; f return ` - -
- + ${escapeHtml(label)}
+ + + + +
- +
- - -
- ANOUMA + + ANOUMA
+ ${bodyHtml}
- ${escapeHtml(footerText)} + + ${escapeHtml(footerText)}
@@ -76,5 +83,5 @@ export function renderEmailShell(args: { preheader?: string; bodyHtml: string; f export function paragraph(text: string, opts: { italic?: boolean; small?: boolean } = {}): string { const size = opts.small ? "13px" : "16px"; const style = opts.italic ? "font-style:italic;" : ""; - return `

${escapeHtml(text)}

`; + return `

${escapeHtml(text)}

`; } diff --git a/lib/email/payloadAdapter.ts b/lib/email/payloadAdapter.ts index 24180a5..ec82819 100644 --- a/lib/email/payloadAdapter.ts +++ b/lib/email/payloadAdapter.ts @@ -1,5 +1,5 @@ import type { PayloadEmailAdapter } from "payload"; -import { getMailFrom, getMailTransport } from "./transport"; +import { getMailEnvelopeFrom, getMailFrom, getMailTransport } from "./transport"; /** Connects Payload's built-in auth emails to the same SMTP transport as ANOUMA's own emails. */ export const payloadEmailAdapter: PayloadEmailAdapter = () => ({ @@ -9,7 +9,8 @@ export const payloadEmailAdapter: PayloadEmailAdapter = () => ({ sendEmail: async (message) => { return getMailTransport().sendMail({ ...message, - from: message.from || getMailFrom(), + from: getMailFrom(), + envelope: { from: getMailEnvelopeFrom() }, }); }, }); diff --git a/lib/email/reminderTemplate.ts b/lib/email/reminderTemplate.ts index 0836cd7..4030974 100644 --- a/lib/email/reminderTemplate.ts +++ b/lib/email/reminderTemplate.ts @@ -21,34 +21,39 @@ export function renderReminderEmailHtml(data: ReminderEmailData): string { return ` - - + + + + +
- +
- - -
- ANOUMA + + ANOUMA
-

+

+

Hallo ${escapeHtml(recipientName)},

-

+

Dein Online-Termin bei ANOUMA beginnt in ${minutesBefore} Minuten.

- @@ -57,26 +62,26 @@ export function renderReminderEmailHtml(data: ReminderEmailData): string {
-

+

+

Termin:
${escapeHtml(eventTitle)}

-

+

Datum:
${escapeHtml(dateLabel)}

-

+

Uhrzeit:
${escapeHtml(timeLabel)} Uhr

- + Meeting betreten
-

+

Meeting-Passwort:

-

+

${escapeHtml(meetingPassword)}

-

+

Bitte halte dein Passwort bereit. Beim Betreten des Meetings wirst du außerdem nach deinem Namen gefragt.

- Räume für Verbindung - mit dir selbst, miteinander und mit der Natur. + + Räume für Verbindung - mit dir selbst, miteinander und mit der Natur.
diff --git a/lib/email/sendBookingEmails.ts b/lib/email/sendBookingEmails.ts index 49d23f2..5c3f646 100644 --- a/lib/email/sendBookingEmails.ts +++ b/lib/email/sendBookingEmails.ts @@ -1,4 +1,4 @@ -import { getMailFrom, getMailTransport } from "./transport"; +import { getMailEnvelopeFrom, getMailFrom, getMailTransport } from "./transport"; export async function sendEmail( to: string, @@ -8,6 +8,7 @@ export async function sendEmail( const transport = getMailTransport(); await transport.sendMail({ from: getMailFrom(), + envelope: { from: getMailEnvelopeFrom() }, to, subject: template.subject, html: template.html, diff --git a/lib/email/sendReminderEmail.ts b/lib/email/sendReminderEmail.ts index fe5a8c4..9f43b33 100644 --- a/lib/email/sendReminderEmail.ts +++ b/lib/email/sendReminderEmail.ts @@ -1,10 +1,11 @@ -import { getMailFrom, getMailTransport } from "./transport"; +import { getMailEnvelopeFrom, getMailFrom, getMailTransport } from "./transport"; import { renderReminderEmailHtml, renderReminderEmailText, type ReminderEmailData } from "./reminderTemplate"; export async function sendReminderEmail(to: string, data: ReminderEmailData) { const transport = getMailTransport(); await transport.sendMail({ from: getMailFrom(), + envelope: { from: getMailEnvelopeFrom() }, to, subject: `Dein Online-Termin bei ANOUMA beginnt in ${data.minutesBefore} Minuten`, html: renderReminderEmailHtml(data), diff --git a/lib/email/transport.ts b/lib/email/transport.ts index b0b898c..4d51c6a 100644 --- a/lib/email/transport.ts +++ b/lib/email/transport.ts @@ -30,5 +30,15 @@ export function getMailTransport(): nodemailer.Transporter { } export function getMailFrom(): string { - return process.env.SMTP_FROM || "ANOUMA "; + const configured = (process.env.SMTP_FROM || process.env.SMTP_USER || "support@anouma.org").trim(); + const unquoted = configured.replace(/^("|')(.*)\1$/, "$2").trim(); + + // Keep the address in the message header, but show only ANOUMA in mail clients. + if (unquoted.includes("<") && unquoted.includes(">")) return unquoted; + return `ANOUMA <${unquoted}>`; +} + +/** Envelope sender used by strict SMTP relays for sender ownership checks. */ +export function getMailEnvelopeFrom(): string { + return process.env.SMTP_USER || "support@anouma.org"; } diff --git a/setup.sh b/setup.sh index 588b396..be7d9ba 100755 --- a/setup.sh +++ b/setup.sh @@ -353,7 +353,7 @@ if [ -z "$(env_get NEXT_PUBLIC_SERVER_URL)" ]; then DOMAIN="$(ask "Domain" "anouma.org" "DOMAIN")" APP_URL="$(ask "Application URL" "https://$DOMAIN" "APP_URL")" env_set NEXT_PUBLIC_SERVER_URL "$APP_URL" - [ -z "$(env_get SMTP_FROM)" ] && env_set SMTP_FROM "ANOUMA " + [ -z "$(env_get SMTP_FROM)" ] && env_set SMTP_FROM "no-reply@${DOMAIN}" else log_info "NEXT_PUBLIC_SERVER_URL bereits vorhanden - unverändert." fi