Skip to content

Added automated emails design API endpoint#27065

Merged
cmraible merged 5 commits intomainfrom
chris-ny-1138-add-automated-email-design-settings-api-endpoint
Apr 1, 2026
Merged

Added automated emails design API endpoint#27065
cmraible merged 5 commits intomainfrom
chris-ny-1138-add-automated-email-design-settings-api-endpoint

Conversation

@cmraible
Copy link
Copy Markdown
Collaborator

@cmraible cmraible commented Apr 1, 2026

closes https://linear.app/ghost/issue/NY-1138/add-automated-email-design-settings-api-endpoint

Summary

  • Added a singleton automated email design Admin API subresource at GET and PUT /automated_emails/design for reading and editing the shared automated email design settings
  • The endpoint resolves the shared default design setting by its slug (default-automated-email) rather than requiring clients to know the ID
  • Reuses email_design_setting permissions (admin/owner only) and prevents slug modification
  • Keeps email_design_settings as an internal persistence detail instead of exposing a generic table-shaped API
  • Moved DEFAULT_EMAIL_DESIGN_SETTING_SLUG constant to the shared constants.js module to avoid duplication

Advantages over a more generic email-design-settings BREAD resource

  • For welcome emails customization, we only really need read and edit of the default row. Creating a full BREAD resource is more code to maintain and support, some of which isn't necessary for our current implementation.
  • Better decoupling between our DB schema and the frontend. The frontend doesn't need to know or care about the email_design_settings table, it only cares about "editing design settings for welcome emails". Ultimately this means more flexibility in changing the schema in the future without breaking the API
  • Simpler implementation on both the backend and frontend

Test plan

  • E2E tests covering read, edit, slug rejection, field validation, and role-based permissions (editor, author, contributor denied)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e89de4b-4f63-493f-923f-03ba9e76cbe2

📥 Commits

Reviewing files that changed from the base of the PR and between 73fc2a1 and 4fcd058.

📒 Files selected for processing (1)
  • ghost/core/test/e2e-api/admin/automated-email-design.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/test/e2e-api/admin/automated-email-design.test.js

Walkthrough

Adds a new API controller for automated email design with read and edit handlers, and exposes it via a new automatedEmailDesign endpoint in the endpoints index. Registers admin routes GET /automated_emails/design and PUT /automated_emails/design. Extracts DEFAULT_EMAIL_DESIGN_SETTING_SLUG into member-welcome-emails constants and updates usage. Adds end-to-end tests covering read/edit, validation, id handling, and permission checks.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically describes the main change: adding a new automated emails design API endpoint.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about the new API endpoint, its design rationale, and testing approach.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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-1138-add-automated-email-design-settings-api-endpoint

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.

@cmraible cmraible changed the title Added automated-email-design endpoint Added automated emails design API endpoint Apr 1, 2026
@cmraible
Copy link
Copy Markdown
Collaborator Author

cmraible commented Apr 1, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 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.

1 similar comment
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 1, 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: 2

🧹 Nitpick comments (1)
ghost/core/test/e2e-api/admin/automated-email-design.test.js (1)

90-144: Exercise both permission actions for each non-admin role.

Lines 91-143 only assert GET for editor/author and PUT for contributor, but ghost/core/core/server/api/endpoints/automated-email-design.js:39-55 gates read and edit separately. A permission or route miswire on contributor-read or editor/author-edit would still pass this suite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/e2e-api/admin/automated-email-design.test.js` around lines 90
- 144, Tests currently only exercise one permission action per role
(editor/author GET, contributor PUT) while the automated-email-design endpoint
enforces separate 'read' and 'edit' permissions; update the tests so each
non-admin role calls both agent.get('automated_emails/design') and
agent.put('automated_emails/design') with the same expectation of 403 and the
same snapshot assertions. Locate the three tests using loginAsEditor,
loginAsAuthor, and loginAsContributor and add the missing counterpart request
for each (editor/author should also call PUT; contributor should also call GET)
reusing the existing .body, .expectStatus(403), .matchBodySnapshot({errors:[{id:
anyErrorId}]}) and .matchHeaderSnapshot({...}) assertions.
🤖 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/api/endpoints/automated-email-design.js`:
- Around line 56-71: The query handler currently allows an incoming payload to
include an id which is passed directly into models.EmailDesignSetting.edit; to
prevent primary-key overwrite, explicitly reject or remove an id in the
singleton payload: in the async query(frame) function (near resolveDefaultDesign
and models.EmailDesignSetting.edit) check if data.id !== undefined and throw a
ValidationError (similar to the slug check) or delete data.id before calling
models.EmailDesignSetting.edit so the defaultDesign id is the only id used when
updating.

In `@ghost/core/test/e2e-api/admin/automated-email-design.test.js`:
- Around line 4-8: The shared matcher matchEmailDesignSetting requires
updated_at to be anyISODateTime but the seeded default row can have updated_at:
null on first read; change matchEmailDesignSetting so updated_at accepts either
anyISODateTime or null (e.g., updated_at: oneOf(anyISODateTime, null) or the
project's equivalent), and apply the same change to the other matchers in this
test file that assert updated_at (the other occurrences of the updated_at
matcher used for initial GET and subsequent edit assertions) so the initial read
passes while edits still validate a real timestamp.

---

Nitpick comments:
In `@ghost/core/test/e2e-api/admin/automated-email-design.test.js`:
- Around line 90-144: Tests currently only exercise one permission action per
role (editor/author GET, contributor PUT) while the automated-email-design
endpoint enforces separate 'read' and 'edit' permissions; update the tests so
each non-admin role calls both agent.get('automated_emails/design') and
agent.put('automated_emails/design') with the same expectation of 403 and the
same snapshot assertions. Locate the three tests using loginAsEditor,
loginAsAuthor, and loginAsContributor and add the missing counterpart request
for each (editor/author should also call PUT; contributor should also call GET)
reusing the existing .body, .expectStatus(403), .matchBodySnapshot({errors:[{id:
anyErrorId}]}) and .matchHeaderSnapshot({...}) assertions.
🪄 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: e9f66c82-a223-4c2f-8de7-01f4de16c744

📥 Commits

Reviewing files that changed from the base of the PR and between bef12af and daee33d.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/automated-email-design.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • ghost/core/core/server/api/endpoints/automated-email-design.js
  • ghost/core/core/server/api/endpoints/index.js
  • ghost/core/core/server/models/automated-email.js
  • ghost/core/core/server/services/member-welcome-emails/constants.js
  • ghost/core/core/server/web/api/endpoints/admin/routes.js
  • ghost/core/test/e2e-api/admin/automated-email-design.test.js

cmraible added 2 commits April 1, 2026 11:58
- Changed missing default design error from NotFoundError to InternalServerError
  since the row is seeded by migration and should always exist
- Fixed slug guard to use 'in' operator, preventing null from bypassing validation
@cmraible cmraible marked this pull request as ready for review April 1, 2026 19:36
@cmraible cmraible requested review from troyciesco April 1, 2026 19:37
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 (2)
ghost/core/test/e2e-api/admin/automated-email-design.test.js (2)

111-165: Consider covering full role × method permission matrix.

Current checks validate denial per role, but not both verbs for each role. A table-driven test for GET and PUT across editor/author/contributor would tighten regression coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/e2e-api/admin/automated-email-design.test.js` around lines
111 - 165, Replace the three separate tests with a table-driven loop inside the
"Permissions" describe that iterates roles (editor, author, contributor) and
HTTP verbs (GET, PUT) to assert 403 for each role×method combination; for each
row call the appropriate agent.loginAsEditor/loginAsAuthor/loginAsContributor,
perform agent.get('automated_emails/design') or
agent.put('automated_emails/design') (for PUT include a minimal body like
{automated_email_design:[{background_color:'dark'}]}), and assert
.expectStatus(403) with the same .matchBodySnapshot and .matchHeaderSnapshot
expectations currently used so every role×verb permutation is covered.

1-1: Prefer Node built-in specifier for assert.

Use node:assert/strict at Line 1 for consistency with Node builtin imports.

Proposed change
-const assert = require('assert/strict');
+const assert = require('node:assert/strict');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/e2e-api/admin/automated-email-design.test.js` at line 1,
Replace the CommonJS require specifier for the assert module so it uses Node's
built-in specifier; locate the top-level require('assert/strict') call and
change it to use the Node built-in specifier (node:assert/strict) to ensure
consistent builtin imports in the test file.
🤖 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/test/e2e-api/admin/automated-email-design.test.js`:
- Around line 111-165: Replace the three separate tests with a table-driven loop
inside the "Permissions" describe that iterates roles (editor, author,
contributor) and HTTP verbs (GET, PUT) to assert 403 for each role×method
combination; for each row call the appropriate
agent.loginAsEditor/loginAsAuthor/loginAsContributor, perform
agent.get('automated_emails/design') or agent.put('automated_emails/design')
(for PUT include a minimal body like
{automated_email_design:[{background_color:'dark'}]}), and assert
.expectStatus(403) with the same .matchBodySnapshot and .matchHeaderSnapshot
expectations currently used so every role×verb permutation is covered.
- Line 1: Replace the CommonJS require specifier for the assert module so it
uses Node's built-in specifier; locate the top-level require('assert/strict')
call and change it to use the Node built-in specifier (node:assert/strict) to
ensure consistent builtin imports in the test file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5dc6db89-1170-45f0-801c-53359fe43cd8

📥 Commits

Reviewing files that changed from the base of the PR and between daee33d and 73fc2a1.

📒 Files selected for processing (2)
  • ghost/core/core/server/api/endpoints/automated-email-design.js
  • ghost/core/test/e2e-api/admin/automated-email-design.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/core/server/api/endpoints/automated-email-design.js

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.

other than the one nit sonarcloud (and coderabbit i guess) had that looks worth fixing, lgtm!

delete data.id;

// Reject slug changes — the slug is an immutable identifier
if ('slug' in data) {
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.

note: i gave some thought to whether we should delete this too if it's included, instead of throwing an error. i think throwing the error is better, because 1) the form on the front end, even if it might send all the design settings every time, prob shouldn't send the slug because it doesn't need to and 2) if the slug is editable later (would it be? don't even know) hitting the error during development will be better instead of silently deleting. And this endpoint would be different by then anyway with 2+ rows in the db.

just mentioning in case you think of a more compelling reason to delete!

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
19.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@cmraible cmraible merged commit c51c2de into main Apr 1, 2026
36 of 37 checks passed
@cmraible cmraible deleted the chris-ny-1138-add-automated-email-design-settings-api-endpoint branch April 1, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants