From 6879b056da3766ecf3f25d8d5a6e598a4d20d337 Mon Sep 17 00:00:00 2001 From: Philip Peterson <1326208+philip-peterson@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:30:44 -0700 Subject: [PATCH] Use sendmail only on dev --- CLAUDE.md | 8 ++++---- Dockerfile | 4 +--- docker/php/entrypoint.sh | 17 ++++++++++------- docker/php/fake-sendmail.sh | 12 +++--------- .../custom/riverside_pt/riverside_pt.module | 9 +++------ 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index afc48c6..3e8ebca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -166,8 +166,8 @@ Nav items come from Drupal's `main` menu. Items titled `"Book An Appointment"` o Booking confirmation emails are sent via `riverside_pt_mail()` in `.module` using the `booking_request` key. Transport is Postmark (via `drupal/symfony_mailer`), configured in `config/sync/symfony_mailer.mailer_transport.postmark.yml`. The API key is injected via the `POSTMARK_API_KEY` environment variable. -In `settings.php` (when the key is present) we also force: -- `mailer_transport.settings.default_transport = postmark` -- `system.mail.interface.default = symfony_mailer` +In `settings.php`: +- If `DEBUG` (dev/localhost): force `system.mail.interface.default = 'php_mail'` (so it uses the mocked sendmail_path below) and never use Postmark. +- Else if `POSTMARK_API_KEY` present (prod): set `mailer_transport.settings.default_transport = postmark` and `system.mail.interface.default = symfony_mailer`. -On localhost/dev, a fake sendmail binary is provided (via Dockerfile + entrypoint) that prints the full email to stderr (visible via `docker compose logs`) instead of failing with "sh: 1: /usr/sbin/sendmail: not found". This catches any legacy `php_mail` fallbacks. Real transactional mail goes through Postmark when configured. +On localhost/dev (when `DEBUG` is truthy), a fake sendmail binary is provided (via Dockerfile + entrypoint) **only in DEBUG mode** that prints the full email to stderr (visible via `docker compose logs`) instead of failing with "sh: 1: /usr/sbin/sendmail: not found". This catches any legacy `php_mail` fallbacks. Real transactional mail goes through Postmark when configured (non-DEBUG). diff --git a/Dockerfile b/Dockerfile index d2ef41b..426f37e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,9 +78,7 @@ COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf.template COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY docker/php/entrypoint.sh /entrypoint.sh -COPY docker/php/fake-sendmail.sh /usr/local/bin/fake-sendmail.sh -RUN chmod +x /entrypoint.sh /usr/local/bin/fake-sendmail.sh && \ - echo 'sendmail_path = /usr/local/bin/fake-sendmail.sh' > /usr/local/etc/php/conf.d/sendmail.ini +RUN chmod +x /entrypoint.sh RUN chown -R www-data:www-data web/sites/default/files && \ chmod -R 755 web/sites/default/files && \ diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index 04424a3..bee8f42 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -65,11 +65,11 @@ npm run build --prefix /var/www/html >/dev/null 2>&1 && echo "[entrypoint] Tailw $DRUSH cache:rebuild >/dev/null 2>&1 && echo "[entrypoint] Cache rebuilt." -# Mock sendmail on localhost/dev: prints full email to stderr (visible in docker logs) -# instead of erroring with "sh: 1: /usr/sbin/sendmail: not found". -# This catches any php_mail / legacy mail() calls (e.g. some webforms, fallbacks). -# Real emails should still go via symfony_mailer + Postmark when configured. -if [ ! -x /usr/local/bin/fake-sendmail.sh ]; then +if [ "${DEBUG:-false}" = "true" ]; then + # Mock sendmail on localhost/dev: prints full email to stderr (visible in docker logs) + # instead of erroring with "sh: 1: /usr/sbin/sendmail: not found". + # This catches any php_mail / legacy mail() calls (e.g. some webforms, fallbacks). + # Real emails should still go via symfony_mailer + Postmark when configured. cat > /usr/local/bin/fake-sendmail.sh << 'FAKE_SENDMAIL' #!/bin/sh echo "=== MOCK SENDMAIL (dev - email logged, not sent) ===" >&2 @@ -83,9 +83,12 @@ exit 0 FAKE_SENDMAIL chmod +x /usr/local/bin/fake-sendmail.sh echo "[entrypoint] Installed fake sendmail for dev logging." + # Override sendmail_path for PHP (affects php_mail interface and any direct mail()). + echo 'sendmail_path = /usr/local/bin/fake-sendmail.sh' > /usr/local/etc/php/conf.d/sendmail.ini 2>/dev/null || true +else + # Ensure no dev mock leaks into prod: remove any sendmail_path override and the fake script. + rm -f /usr/local/etc/php/conf.d/sendmail.ini /usr/local/bin/fake-sendmail.sh 2>/dev/null || true fi -# Override sendmail_path for PHP (affects php_mail interface and any direct mail()). -echo 'sendmail_path = /usr/local/bin/fake-sendmail.sh' > /usr/local/etc/php/conf.d/sendmail.ini 2>/dev/null || true if [ "${DEBUG:-false}" = "true" ]; then NGINX_CSS_CACHE='expires off; add_header Cache-Control "no-store";' diff --git a/docker/php/fake-sendmail.sh b/docker/php/fake-sendmail.sh index 5eeb725..a66968e 100755 --- a/docker/php/fake-sendmail.sh +++ b/docker/php/fake-sendmail.sh @@ -1,15 +1,9 @@ #!/bin/sh -# Fake sendmail for local development. -# Instead of delivering email, it prints the entire message to stderr -# so it appears in `docker compose logs`. -# This prevents "sh: 1: /usr/sbin/sendmail: not found" errors. - -echo "=== MOCK SENDMAIL: email would have been sent (logged instead) ===" >&2 +echo "=== MOCK SENDMAIL (dev - email logged, not sent) ===" >&2 echo "Timestamp: $(date -Iseconds)" >&2 -echo "Args: $*" >&2 -echo "----------------------------------------" >&2 +echo "Called as: $0 $*" >&2 +echo "----- EMAIL CONTENT -----" >&2 cat >&2 echo "" >&2 echo "=== END MOCK SENDMAIL ===" >&2 - exit 0 diff --git a/web/modules/custom/riverside_pt/riverside_pt.module b/web/modules/custom/riverside_pt/riverside_pt.module index 156d6d6..ffaf084 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.module +++ b/web/modules/custom/riverside_pt/riverside_pt.module @@ -84,15 +84,12 @@ function riverside_pt_mail(string $key, array &$message, array $params): void { $message['subject'] = 'Booking request — ' . $start->format('M j, Y g:i A'); $lines = [ - 'Name: ' . $params['first_name'] . ' ' . $params['last_name'], - 'Phone: ' . $params['phone'], + 'Name: ' . ($params['first_name'] ?? '') . ' ' . ($params['last_name'] ?? ''), + 'Email: ' . ($params['email'] ?? ''), + 'Phone: ' . ($params['phone'] ?? ''), 'Slot: ' . $start->format('l, F j, Y') . ', ' . $start->format('g:i A') . '–' . $end->format('g:i A'), ]; - if (!empty($params['email'])) { - array_splice($lines, 1, 0, 'Email: ' . $params['email']); - } - if (!empty($params['comments'])) { $lines[] = 'Comments: ' . $params['comments']; }