All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning. Instead of change type headers, we use module names.
- Segments Enabled Elasticsearch query logging when Debug mode is enabled.
- Segments Fixed low performance of query results processing caused by double unmarshalling of JSON data.
- Segments Improved querying performance by executing Elasticsearch scrolls while the previous scrolls are still being processed.
- Fixed seeding of config options, which were included in the migration, but not present in the
ConfigSeeder. remp/euobserver#268
- Added
bannerClosed,bannerClicked,bannerShownfunction between custom JS params to allow correctly handling banner events. remp/remp#1487 - Added banner dimension option
force_manual_trackingto force manual events tracking for banners using given dimension. remp/remp#1489- When enabled for a dimension, the Track banner events manually checkbox is checked and disabled in the banner form, and the flag is enforced on save.
- Fixed snippet and collection edit forms breaking when a name contains JavaScript-sensitive characters; such snippets can now be opened and fixed. remp/remp#1405
- Changed
campaigns:aggregate-statscommand so it no longer aborts the whole run when only some campaign banners' stats requests to Beam Segments fail; those banners are skipped, the errors are printed. If every banner fails, it still fails loudly (non-zero exit). remp/remp#1491 - Raised the Beam Segments stats client's connect timeout from 1s to 3s, and made
REMP_SEGMENTS_TIMEOUTthe budget for the query alone rather than for connect + query. remp/remp#1491
- Added a
configuration/updatepermission check to the Settings (mailer configuration) page, following the existingbatch/startpattern. As with other privileges, it's unrestricted by default; register it viapermissionManagerinconfig.local.neonto require a role for access — seeMailer/extensions/mailer-module/README.md's "Permission management" section. - Added a per-newsletter-type "Exclude emails from search" option that hides a mail type's templates from the email template search. remp/remp#1462
- Fixed a null error in the newsletter list form when no
systemmail type exists. remp/remp#1462 - IMPORTANT: Added support for partitioning the
mail_logstable by month to keep it performant at scale. Requires a manual migration and permanently scheduled commands afterwards — seeMailer/extensions/mailer-module/docs/MAIL_LOGS_PARTITIONING.md. remp/remp#1481- Added
mail_logs:migrate-to-partitions,mail_logs:backfill-partitions,mail_logs:prune-partitionsandmail_logs:seed-partitionscommands. - Added a persisted daily stats rollup (
mail_template_direct_stats) and amail_logs_stats_statecutoff date so template/newsletter statistics stay accurate after oldmail_logsrows are pruned. mail_template_direct_statsstarts out empty, so the migration requires a one-offmail:aggregate-mail-template-stats --from=<earliest mail_logs date>backfill run beforemail_logs:migrate-to-partitions, or all pre-migration direct-send statistics (in particular, system/transactional templates) will read as 0 forever.mail_logs:migrate-to-partitionsnow refuses to run until this backfill has happened (--forceoverrides) — see the updated runbook's "Migration execution steps".- Added a unique key on
(mail_template_id, date)to bothmail_template_statsandmail_template_direct_stats, and changed theirupsert()to a singleINSERT ... ON DUPLICATE KEY UPDATE. Both tables are read as unbounded lifetime sums, so a duplicate day — which the recommended per-minute + daily--fromschedule produced on every overlap, since the previous implementation was a read-then-write — permanently inflated reported statistics. TheAddUniqueKeyToTemplateStatsTablesmigration removes any pre-existing duplicate rows (keeping the most recently written one) before adding the key. - The partitioned schema is built in a
mail_logs_partitionedshadow table, deliberately not themail_logs_v2name used by the older bigint migration —mail:bigint_migration_cleanup mail_logsunconditionally dropsmail_logs_v2and would otherwise destroy an in-flight migration. - All four commands bound the metadata-lock wait of every DDL statement they run against the live
mail_logs(RENAME TABLE,EXCHANGE PARTITION,REORGANIZE PARTITION,DROP FOREIGN KEY): each attempt waits at most 5 seconds on a session-onlylock_wait_timeout, then withdraws its request so blocked application writes drain, and retries for ~10 minutes before failing cleanly. Without this, MySQL's one-year defaultlock_wait_timeoutturns an unavailable metadata lock into an indefinite hang that queues everymail_logswrite behind it and can exhaustmax_connections. Recommendation: alert on the scheduled commands' exit status — a failed step is now a clean non-zero exit with nothing half-applied, not a stall. mail_logs:prune-partitionsandmail_logs:backfill-partitionsswap stage tables in withEXCHANGE PARTITION, which MySQL rejects unless the stage table's instant-column attributes match the partitioned table's. Since MySQL 8.0.29ADD COLUMN/DROP COLUMNdefault toALGORITHM=INSTANT, so one suchALTERonmail_logspermanently breaks both commands (Non matching attribute 'INSTANT COLUMN(s)' between partition and table) until the whole table is rebuilt. Recommendation: write every future column change onmail_logswith an explicitALGORITHM=INPLACE— see the runbook's "Other behavioral notes" for the one-line repair if it does happen.mail_logs.created_at/updated_atchange fromTIMESTAMP(UTC-normalised) toDATETIME(wall-clock), somail_logs:migrate-to-partitionsconverts them using its sessiontime_zone, which it now pins to PHP's configured zone by name rather than to a fixed UTC offset. A fixed offset applies today's DST state to the whole history: on one real 3.7M-row dataset that shifted 1.1M rows by an hour, 219 of them onto the wrong calendar day and 7 into the wrong month partition. Named zones require MySQL's time-zone tables to be populated; the command falls back to the fixed offset with a loud warning if they are not. Recommendation: runmysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysqlbefore the migration — and if the session zone changes between runs, truncatemail_logs_partitionedand clear themail_logs_partitions_migration_tier_*_last_idRedis keys first, since the composite(id, created_at)primary key makes a re-copy under a different converted timestamp a duplicate id rather than a no-op.mail_logs:migrate-to-partitionsdrops themail_log_conversions→mail_logsforeign key after the table swap (against the frozenmail_logs_old) rather than before it, and its phases are renumbered to a gapless Phase 0–8. An FK drop takes a metadata lock on the parent table that concurrent writes can starve indefinitely, so doing it against the live table risked taking the database down. Recommendation: pauseworker:mail,worker:hermesand themail:mailgun-eventscron for the duration of the go-live run — the runbook's new "Locking behaviour" section lists every writer that needs pausing, including the two API endpoints that cannot be stopped by stopping a process.
- Added
- BREAKING: Removed the
hard_bounced_atcolumn frommail_logs, and the matchinghard_bounced_atfilter/response field from themail:logsandmail:logs-count-per-statusAPI endpoints. remp/remp#1481- Use
dropped_atinstead — bounce events are now recorded there.
- Use
- BREAKING: Removed
LogsRepository::getNonBatchTemplateStats(). remp/remp#1481- Use
MailTemplateDirectStatsRepository::sumForTemplates()instead; it reads a daily precomputed rollup (populated bymail:aggregate-mail-template-stats) rather than live-scanningmail_logs, and no longer exposes ahard_bouncedkey.
- Use
- BREAKING: Changed
mail:aggregate-mail-template-statsto take--date=/--from=options instead of a positionaldateargument (including relative values liketoday/tomorrow), and changed its no-option default from "yesterday" to "yesterday and today". remp/remp#1481- Update any crontab/scheduler entries:
mail:aggregate-mail-template-stats YYYY-MM-DDbecomesmail:aggregate-mail-template-stats --date=YYYY-MM-DD(or--from=YYYY-MM-DDto recompute/backfill a range, including today); atoday/tomorrowpair of entries becomes a single baremail:aggregate-mail-template-statsentry, since the new default already covers both. Add a second, daily entry with--from=$(date -d '-30 days' +%Y-%m-%d)so opens/clicks/conversions that arrive after a day was first aggregated are still picked up — see the updated runbook's "Permanently scheduled" section.
- Update any crontab/scheduler entries:
- BREAKING:
mail:aggregate-mail-template-statsnow refuses a--date=/clamps a--from=that reaches below the activemail_logs_stats_statecutoff date, sincemail_logsno longer holds complete data before it once pruning has run; pass--forcefor a deliberate override. remp/remp#1481- Only affects installations that have completed the
mail_logspartitioning migration and started pruning; no effect otherwise (the cutoff date stays unset).
- Only affects installations that have completed the
- BREAKING: Dropped all foreign keys to/from
mail_logs(InnoDB forbids FKs on partitioned tables); its primary key is now composite(id, created_at)instead ofid. remp/remp#1481- If you have custom modules/repositories doing implicit Nette DB joins against
mail_logs(e.g.->where('mail_template.code', ...)), either register aConventionsservice that knows these relations (seeModels/Database/PartitionedConventions.phpfor the pattern) or rewrite them as explicitWHEREconditions on the FK id column — the defaultDiscoveredConventionsFK-based join discovery no longer resolves joins to/from this table.
- If you have custom modules/repositories doing implicit Nette DB joins against
- BREAKING: Removed the
mail:migrate-mail-logs-and-conversionscommand (the second step of themail_logs/mail_log_conversionsbigint migration for installations older than 1.1.0), along with itslogConversionsRepositorydual-write configuration. remp/remp#1481- If that migration never completed on your installation —
mail_logs.user_idmissing, or a leftovermail_logs_v2/mail_log_conversions_v2/mail_logs_oldtable — complete it on a pre-5.2.0 release before upgrading. The partitioning migration hard-requires it (it copiesmail_logs.user_id), soCreatePartitionedMailLogsTableandmail_logs:migrate-to-partitionsboth abort with instructions instead of failing halfway through.
- If that migration never completed on your installation —
- BREAKING:
mail:bigint_migration_cleanup mail_logsnow refuses to dropmail_logs_oldwhilemail_logs_backfill_statestill haspendingmonth partitions. remp/remp#1481mail_logs_oldis the only sourcemail_logs:backfill-partitionscan read historical months from, so dropping it early loses them irrecoverably. Finish the backfill first, or drop the table manually if you have deliberately abandoned it.
- v5.1
- v5.0
- v4.3
- v4.2
- v4.1
- v4.0
- v3.11
- v3.10
- v3.9
- v3.8
- v3.7
- v3.6
- v3.5
- v3.4
- v3.3
- v3.2
- v3.1
- v3.0
- v2.2
- v2.1
- v2.0
- v1.2
- v1.1
- v1.0
- v0.*