Skip to content

Added migration to create default email_design_settings row#26975

Open
cmraible wants to merge 10 commits intomainfrom
chris-ny-1179-insert-default-email-template-row
Open

Added migration to create default email_design_settings row#26975
cmraible wants to merge 10 commits intomainfrom
chris-ny-1179-insert-default-email-template-row

Conversation

@cmraible
Copy link
Copy Markdown
Collaborator

@cmraible cmraible commented Mar 26, 2026

closes https://linear.app/ghost/issue/NY-1179/insert-default-email-design-settings-row

Summary

Seeds a default row into the email_design_settings table. This row will be referenced by all existing automated_emails rows when the FK is added in a subsequent migration.

Changes

  • Added DML migration to insert a default email_design_settings row with slug default-automated-email, using schema defaults for all design columns. Idempotent — skips if the row already exists.
  • Added EmailDesignSetting Bookshelf model (required by the fixture system)
  • Added fixture entry in fixtures.json so new databases created via knex-migrator init also get the default row
  • Updated fixtures integrity hash

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change introduces email design settings functionality to the Ghost platform. A new EmailDesignSetting Bookshelf model is created with a corresponding database table. Fixture data is added to define default email design settings with styling properties including colors, fonts, button styles, and image options. A database migration is introduced to populate the initial default email design settings record. The fixtures hash is updated to reflect these changes, and the package version is incremented to 6.25.0-rc.0.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a migration to create a default email_design_settings row, which is the primary objective of the PR.
Description check ✅ Passed The description is well-related to the changeset, providing context about the migration, the Bookshelf model, fixture entry, and explaining the purpose and idempotent nature of the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chris-ny-1179-insert-default-email-template-row

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the migration [pull request] Includes migration for review label Mar 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

It looks like this PR contains a migration 👀
Here's the checklist for reviewing migrations:

General requirements

  • ⚠️ Tested performance on staging database servers, as performance on local machines is not comparable to a production environment
  • Satisfies idempotency requirement (both up() and down())
  • Does not reference models
  • Filename is in the correct format (and correctly ordered)
  • Targets the next minor version
  • All code paths have appropriate log messages
  • Uses the correct utils
  • Contains a minimal changeset
  • Does not mix DDL/DML operations
  • Tested in MySQL and SQLite

Schema changes

  • Both schema change and related migration have been implemented
  • For index changes: has been performance tested for large tables
  • For new tables/columns: fields use the appropriate predefined field lengths
  • For new tables/columns: field names follow the appropriate conventions
  • Does not drop a non-alpha table outside of a major version

Data changes

  • Mass updates/inserts are batched appropriately
  • Does not loop over large tables/datasets
  • Defends against missing or invalid data
  • For settings updates: follows the appropriate guidelines

@cmraible cmraible changed the base branch from main to chris-ny-1137-create-email_templates-table-migration March 26, 2026 05:55
@cmraible cmraible marked this pull request as ready for review March 26, 2026 05:58
@@ -0,0 +1,30 @@
const ghostBookshelf = require('./base');
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: creating the model is required for the fixtures to work

yarn knex-migrator init doesn't run all the migrations in the versions directory, so we have to add any default data like this in the migration (for existing sites) and the fixtures (for new sites). The fixture manager depends on the model, hence including it in this PR.

@cmraible
Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/core/server/models/email-design-setting.js`:
- Around line 1-30: Add the EmailDesignSetting model to the models index by
importing the model and exporting it from the central models registry so
fixtures can load it; specifically, require the exported EmailDesignSetting (the
value produced by ghostBookshelf.model('EmailDesignSetting',
EmailDesignSetting)) in the models index and add it to module.exports (e.g.,
exports.EmailDesignSetting = require(...).EmailDesignSetting or equivalent) so
the symbol EmailDesignSetting is registered alongside the other models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d55c4294-9340-4132-b6e4-9ae5e8f741ad

📥 Commits

Reviewing files that changed from the base of the PR and between c6f60ef and c76a362.

📒 Files selected for processing (4)
  • ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-05-50-35-insert-default-email-design-settings-row.js
  • ghost/core/core/server/data/schema/fixtures/fixtures.json
  • ghost/core/core/server/models/email-design-setting.js
  • ghost/core/test/unit/server/data/schema/integrity.test.js

@cmraible cmraible requested review from EvanHahn and troyciesco March 26, 2026 06:36
return;
}

await knex('email_design_settings').insert({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd meant to bring this up yesterday: would it make sense to seed these styles from the first active newsletter (avoiding saying "default" lol) if it exists?

one spot it could get clunky is if there's something like a header image in the newsletter, it might not make sense in the welcome email.

i think seeding like how you have it is probably right but just wanted to bring it up before we ship this piece. A "copy design from newsletter" button would probably be a better way to smooth out the experience for people that do want stuff the same or want to save most of the effort of copying colors etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The risk of this, I think, is if someone already has a welcome email set up, they might be surprised to see its design change. But you could make the argument the other way too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think I agree that this is probably right as-is, to avoid surprises with the welcome email design changing without any user intervention.

A "copy design from newsletter" button would probably be a better way to smooth out the experience for people that do want stuff the same or want to save most of the effort of copying colors etc.

This is a good idea though, might be something we can fit in before GA

Copy link
Copy Markdown
Contributor

@EvanHahn EvanHahn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few non-blocking comments.

Comment on lines +13 to +16
if (existing) {
logging.warn('Default email_design_settings row already exists, skipping');
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: When would this ever happen? If it did, should we throw an error instead?

Copy link
Copy Markdown
Collaborator Author

@cmraible cmraible Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this ever happen?

In the normal course of a Ghost site, it's highly unlikely that it would, so honest answer is probably never.

If it did, should we throw an error instead?

Good question - I think not, but I'm happy to be challenged on that. Rationale for not throwing an error: it doesn't hurt anything to no-op here: if the row already exists, nothing really breaks in Ghost.

If we instead throw an error, it would block any subsequent migrations from running, and prevent Ghost from booting. I do think there's a valid fail-fast argument to be made that preventing Ghost from booting if the schema/data is in an unexpected state is the right thing to do, which would be an interesting discussion. Practically speaking though, I'm mostly just following the existing conventions in previous migrations:

rg -U 'if \(existing\w*\).*\n.*\n.*return;' ghost/core/core/server/data/migrations/versions/

ghost/core/core/server/data/migrations/versions/5.40/2023-03-21-19-02-add-self-serve-integration-api-key.js
39:        if (existingKey) {
40:            logging.warn('Admin API key for "Self-Serve Migration Integration" already exists');
41:            return;

ghost/core/core/server/data/migrations/versions/6.14/2026-01-15-12-01-00-add-transistor-integration-api-key.js
37:        if (existingKey) {
38:            logging.warn('Admin API key already exists for Transistor integration');
39:            return;

ghost/core/core/server/data/migrations/versions/5.40/2023-03-21-18-42-add-self-serve-integration-role.js
14:        if (existingRole) {
15:            logging.warn('"Self-Serve Migration Integration" role already exists, skipping');
16:            return;

ghost/core/core/server/data/migrations/versions/6.14/2026-01-15-12-00-00-add-transistor-integration.js
14:        if (existing) {
15:            logging.warn('Found existing Transistor integration');
16:            return;

ghost/core/core/server/data/migrations/versions/5.113/2025-03-07-12-24-00-add-super-editor.js
14:        if (existingRole) {
15:            logging.warn('"Super Editor" role already exists, skipping');
16:            return;

ghost/core/core/server/data/migrations/versions/5.40/2023-03-21-18-52-add-self-serve-integration.js
15:        if (existingIntegration) {
16:            logging.warn('Integration already exists, skipping');
17:            return;

ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-05-50-35-insert-default-email-design-settings-row.js
13:        if (existing) {
14:            logging.warn('Default email_design_settings row already exists, skipping');
15:            return;

ghost/core/core/server/data/migrations/versions/5.3/2022-07-06-09-26-add-ghost-explore-integration-api-key.js
39:        if (existingKey) {
40:            logging.warn('Admin API key already exists');
41:            return;

ghost/core/core/server/data/migrations/versions/5.100/2024-11-06-04-45-15-add-activitypub-integration.js
17:        if (existing) {
18:            logging.warn('Found existing Ghost ActivityPub integration');
19:            return;

ghost/core/core/server/data/migrations/versions/5.3/2022-07-06-07-58-add-ghost-explore-integration-role.js
14:        if (existingRole) {
15:            logging.warn('"Ghost Explore Integration" role already exists, skipping');
16:            return;

ghost/core/core/server/data/migrations/versions/5.3/2022-07-06-09-17-add-ghost-explore-integration.js
15:        if (existingIntegration) {
16:            logging.warn('Integration already exists, skipping');
17:            return;

return;
}

await knex('email_design_settings').insert({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The risk of this, I think, is if someone already has a welcome email set up, they might be surprised to see its design change. But you could make the argument the other way too.

Comment on lines +6 to +8
/**
* @returns {object} Default values for email design settings columns
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This reduces type safety, I believe. TypeScript can infer the value that's being returned here. But if you specify it as object, all it'll know is that it's some object. I think it's worth removing this entire annotation.

Suggested change
/**
* @returns {object} Default values for email design settings columns
*/

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good shout, TIL!

Copy link
Copy Markdown
Contributor

@troyciesco troyciesco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropping the same "request changes" here as the parent pr just to make sure we bump the migration verison

@troyciesco troyciesco force-pushed the chris-ny-1137-create-email_templates-table-migration branch 2 times, most recently from 2bf276e to b24b3ad Compare March 26, 2026 16:32
Base automatically changed from chris-ny-1137-create-email_templates-table-migration to main March 26, 2026 17:44
@cmraible cmraible force-pushed the chris-ny-1179-insert-default-email-template-row branch from c76a362 to 937a040 Compare March 26, 2026 19:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ghost/core/test/integration/exporter/exporter.test.js (1)

40-44: ⚠️ Potential issue | 🔴 Critical

Duplicate entry: email_design_settings appears twice in the tables array.

Line 40 already contains 'email_design_settings', and line 44 adds it again. This duplicate will cause the test to fail because Object.keys(exportData.data) returns unique keys while the tables array will have an extra entry.

Remove the duplicate entry at line 44.

Proposed fix
                'email_spam_complaint_events',
-               'email_design_settings',
                'emails',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/integration/exporter/exporter.test.js` around lines 40 - 44,
The tests fail because the tables array contains a duplicate entry
'email_design_settings'; locate the tables array in exporter.test.js (the array
named tables) and remove the duplicate 'email_design_settings' entry so each
table name appears only once, ensuring Object.keys(exportData.data) matches the
tables list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@ghost/core/test/integration/exporter/exporter.test.js`:
- Around line 40-44: The tests fail because the tables array contains a
duplicate entry 'email_design_settings'; locate the tables array in
exporter.test.js (the array named tables) and remove the duplicate
'email_design_settings' entry so each table name appears only once, ensuring
Object.keys(exportData.data) matches the tables list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cc5d07e-d331-47a1-8c47-2ade79f355cb

📥 Commits

Reviewing files that changed from the base of the PR and between c76a362 and 937a040.

📒 Files selected for processing (6)
  • ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-01-56-35-add-email-design-settings-table.js
  • ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-05-50-35-insert-default-email-design-settings-row.js
  • ghost/core/core/server/data/schema/fixtures/fixtures.json
  • ghost/core/core/server/models/email-design-setting.js
  • ghost/core/test/integration/exporter/exporter.test.js
  • ghost/core/test/unit/server/data/schema/integrity.test.js
✅ Files skipped from review due to trivial changes (3)
  • ghost/core/test/unit/server/data/schema/integrity.test.js
  • ghost/core/core/server/models/email-design-setting.js
  • ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-01-56-35-add-email-design-settings-table.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/core/server/data/schema/fixtures/fixtures.json
  • ghost/core/core/server/data/migrations/versions/6.23/2026-03-26-05-50-35-insert-default-email-design-settings-row.js

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@ghost/core/core/server/data/migrations/versions/6.24/2026-03-26-15-47-00-insert-default-email-design-settings-row.js`:
- Around line 19-20: The migration's down deletes by slug and can remove
pre-existing rows if up was skipped; change it so the migration only deletes the
exact row it inserted: generate and store the new id in a module-scoped variable
when inserting (e.g., INSERTED_ID), set it only when up actually performs the
insert (use DEFAULT_SLUG and the (new ObjectID()).toHexString() value), and
update down to delete by that stored id (or no-op if INSERTED_ID is undefined)
instead of deleting by slug.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62c623cc-e3d1-40f1-ac13-1a80e256bf27

📥 Commits

Reviewing files that changed from the base of the PR and between 832f037 and 9fcc159.

📒 Files selected for processing (1)
  • ghost/core/core/server/data/migrations/versions/6.24/2026-03-26-15-47-00-insert-default-email-design-settings-row.js

@cmraible cmraible force-pushed the chris-ny-1179-insert-default-email-template-row branch from 2e460d5 to f0ff03d Compare March 27, 2026 20:52
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
ghost/core/core/server/data/migrations/versions/6.25/2026-03-26-15-47-00-insert-default-email-design-settings-row.js (1)

18-35: Prefer DB defaults here to reduce drift risk.

These fields duplicate schema defaults. If defaults evolve, migration behavior can diverge from schema/fixture expectations. Consider inserting only required non-default fields (id, slug, created_at).

Suggested simplification
         await knex('email_design_settings').insert({
             id: (new ObjectID()).toHexString(),
             slug: DEFAULT_SLUG,
-            background_color: 'light',
-            header_background_color: 'transparent',
-            show_header_title: true,
-            button_color: 'accent',
-            button_corners: 'rounded',
-            button_style: 'fill',
-            link_color: 'accent',
-            link_style: 'underline',
-            body_font_category: 'sans_serif',
-            title_font_category: 'sans_serif',
-            title_font_weight: 'bold',
-            image_corners: 'square',
-            show_badge: true,
             created_at: knex.raw('current_timestamp')
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ghost/core/core/server/data/migrations/versions/6.25/2026-03-26-15-47-00-insert-default-email-design-settings-row.js`
around lines 18 - 35, The migration currently inserts many columns that
duplicate schema defaults; change the insert into the email_design_settings
table to only provide the required non-default fields (keep id via (new
ObjectID()).toHexString(), slug using DEFAULT_SLUG, and created_at if you need
an explicit timestamp—otherwise omit created_at to use the DB default) and
remove the columns that mirror schema defaults (background_color,
header_background_color, show_header_title, button_color, button_corners,
button_style, link_color, link_style, body_font_category, title_font_category,
title_font_weight, image_corners, show_badge) so future schema default changes
won’t drift from this migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@ghost/core/core/server/data/migrations/versions/6.25/2026-03-26-15-47-00-insert-default-email-design-settings-row.js`:
- Around line 18-35: The migration currently inserts many columns that duplicate
schema defaults; change the insert into the email_design_settings table to only
provide the required non-default fields (keep id via (new
ObjectID()).toHexString(), slug using DEFAULT_SLUG, and created_at if you need
an explicit timestamp—otherwise omit created_at to use the DB default) and
remove the columns that mirror schema defaults (background_color,
header_background_color, show_header_title, button_color, button_corners,
button_style, link_color, link_style, body_font_category, title_font_category,
title_font_weight, image_corners, show_badge) so future schema default changes
won’t drift from this migration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 77e96f75-13e4-40df-aae3-c701159e646d

📥 Commits

Reviewing files that changed from the base of the PR and between 9fcc159 and f0ff03d.

📒 Files selected for processing (5)
  • ghost/core/core/server/data/migrations/versions/6.25/2026-03-26-15-47-00-insert-default-email-design-settings-row.js
  • ghost/core/core/server/data/schema/fixtures/fixtures.json
  • ghost/core/core/server/models/email-design-setting.js
  • ghost/core/package.json
  • ghost/core/test/unit/server/data/schema/integrity.test.js
✅ Files skipped from review due to trivial changes (3)
  • ghost/core/package.json
  • ghost/core/test/unit/server/data/schema/integrity.test.js
  • ghost/core/core/server/data/schema/fixtures/fixtures.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/server/models/email-design-setting.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

migration [pull request] Includes migration for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants