Send mail locally again: message_queue + PHPMailer over SMTP
Outbound email was relayed to the hub's /notify endpoint, so this install had
no SMTP configuration of its own and no way to see what had been sent. Bring
delivery back in-house, following the fotografia.andmore.pt stack rather than
inventing a second shape for it.
- Submodule bumped 603ff84 -> ea3616a, the pin fotografia already uses. That
is what provides functions_mail.php (shared transport), functions_settings.php
(platform_smtp_config + encrypted settings) and compat_secrets.php. Verified
no function-name collisions with this repo's own config/functions_settings.php:
platform-tools namespaces everything platform_*.
- PHPMailer 7.1.1 vendored at vendor/PHPMailer/src/. platform_mail_available()
looks for exactly this hand-vendored layout before falling back to composer's
autoloader, so the host needs no shell and no composer install. LGPL-2.1
license text included.
- config/functions_queue.php replaced: was a thin facade over the hub relay,
now fotografia's real queue. Rows are claimed atomically, retried with
exponential backoff up to max_attempts, and park in 'failed' with the real
SMTP rejection in last_error.
- SMTP resolution matches the fleet: .env wins for the SMTP_* constants, while
the actual send goes through platform_smtp_config($pdo), which is DB-first.
That ordering is deliberate -- the database is the half an operator can
change from the backoffice without shell access. Empty SMTP_HOST falls back
to PHP mail().
- Sistema -> Comunicações (views/sysadmin/comms.php): credentials plus a live
SMTP connection test and a verdict on what would happen to a message sent
right now. Secret-valued settings are write-only in the form and encrypted
at rest (AES-256-GCM, key from .env, never in the DB dump). Which keys count
as secret is platform_settings_is_secret()'s decision, not a list kept here.
- Sistema -> Mensagens (views/sysadmin/messages.php): the queue monitor.
Filters, per-message detail with last_error, and retry/cancel/archive.
Retry flushes inline so the operator sees the outcome immediately.
- cron.php drains the queue every 5 minutes, serialised with a lock file, and
runs retention once a day. Over HTTP it answers 404 until CRON_TOKEN is set
to something other than replace_me. Deliberately not a copy of fotografia's
cron: the session-reminder, download-digest and photo-archive steps have no
modules here and would be dead code.
- config/functions_notify_client.php deleted, and ANDMORE_ID_URL with it --
the hub relay was its only caller.
Verified without a database: PHP lint across the tree; PHPMailer builds a full
MIME message with RFC 2047 UTF-8 subject encoding from the three vendored files;
platform_mail_available() finds them; platform_mail_make() returns a correctly
configured mailer; the diagnosis states report Operacional / Não configurado.
NOT verified: the settings encryption round-trip -- this machine's PHP has no
openssl extension, so platform_secret_key() returns null and values pass through
in clear. The code degrades safely (stores the value rather than losing it) and
the Comunicações page warns when the key is unavailable.
Deployment: apply database/migration_20260913_message_queue.sql, set SMTP in
Comunicações or .env, set APP_SECRET_KEY, and schedule cron.php.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3626080599
commit
d5cb50b432
16 files changed
+8982
-136
No files matched your search
+35
-18
@@ -17,27 +17,44 @@ DB_NAME=fitme3070
|
||||
DB_USER=root
|
||||
DB_PASS=
|
||||
|
||||
# ─── Notifications (email + SMS) ─────────────────────────────────────────────
|
||||
# Delivery is centralized at id.andmore.pt (/notify). This app does not send
|
||||
# mail/SMS itself. Auth + endpoint are ANDMORE_ID_URL + NOTIFY_API_KEY below.
|
||||
# ─── Email / SMTP ───────────────────────────────────────────────
|
||||
# This app sends its own mail again (PHPMailer over SMTP) — nothing is relayed
|
||||
# through the hub any more. Anything set here WINS over the same setting in
|
||||
# Sistema → Comunicações, so leave these blank if you want to manage SMTP from
|
||||
# the backoffice. Empty SMTP_HOST falls back to PHP's mail().
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_SECURE=tls
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
|
||||
# ─── Backup ─────────────────────────────────────────────────────────────────
|
||||
BACKUP_TOKEN=replace_me
|
||||
BACKUP_KEEP_DAYS=7
|
||||
# ─── Encryption key for settings stored in the DB ─────────────────────────
|
||||
# smtp_password / twilio_token are AES-256-GCM encrypted at rest so they are not
|
||||
# readable in a database dump. Without a key they are stored in plain text and
|
||||
# the Comunicações page warns about it. Any long random string works.
|
||||
# (BACKUP_TOKEN is used as a fallback when this is unset.)
|
||||
APP_SECRET_KEY=
|
||||
|
||||
# ─── Andmore hub (notifications only) ────────────────────────────────────────
|
||||
# This install owns its own login: passwords live in users.password_hash and
|
||||
# views/auth/login.php is a real form. The Andmore ID / SSO delegation was
|
||||
# removed — no IDENTITY_MODE, no SSO_* keys, no central sso_sessions table.
|
||||
#
|
||||
# ANDMORE_ID_URL stays only because outbound email/SMS is still relayed through
|
||||
# the hub's /notify endpoint (see NOTIFY_API_KEY below).
|
||||
ANDMORE_ID_URL=https://hub.andmore.pt/
|
||||
# ─── Twilio (SMS / WhatsApp) — optional ───────────────────────────────
|
||||
# Leave empty unless the sms/whatsapp channels are actually used. Email works
|
||||
# without them.
|
||||
TWILIO_SID=
|
||||
TWILIO_TOKEN=
|
||||
TWILIO_SMS_FROM=
|
||||
TWILIO_WHATSAPP_FROM=
|
||||
|
||||
# ─── Notificações centralizadas (id.andmore.pt /notify) ──────────────────────
|
||||
# App key for POST {ANDMORE_ID_URL}notify/send. Minted on id.andmore.pt →
|
||||
# Notificações → Chaves de app.
|
||||
NOTIFY_API_KEY=
|
||||
# ─── Dev safety nets (ignored when APP_ENV=prod) ───────────────────────
|
||||
# EMAIL_DEV_REDIRECT — send every message here instead of the real recipient.
|
||||
# EMAIL_DEV_PRINT — skip SMTP entirely, log to logs/message_dev.log. Wins.
|
||||
EMAIL_DEV_REDIRECT=
|
||||
EMAIL_DEV_PRINT=false
|
||||
|
||||
# ─── Cron ────────────────────────────────────────────────────────
|
||||
# cron.php drains the message queue every 5 minutes. Over the shell no token is
|
||||
# needed. Over HTTP it is the only protection, and cron.php answers 404 until it
|
||||
# is set to something other than replace_me:
|
||||
# */5 * * * * curl -fsS "https://fitme3070.pt/cron.php?token=<CRON_TOKEN>"
|
||||
CRON_TOKEN=replace_me
|
||||
|
||||
# ─── Hub social content endpoint (api/hub-content.php) ─────────────────────────
|
||||
# Shared secret the hub social-post generator signs its pull requests with.
|
||||
|
||||
Reference in new issue
Block a user