fix(email): allow larger email templates

This commit is contained in:
Sony Mathew
2026-07-23 22:00:37 +05:30
parent a98666030b
commit b85a8de43e
11 changed files with 73 additions and 12 deletions
+2
View File
@@ -22,6 +22,7 @@
class EmailTemplate < ApplicationRecord
BRANDED_LAYOUT_NAME = 'base'.freeze
DEFAULT_LOCALE = 'en'.freeze
MAX_BODY_LENGTH = 256.kilobytes
CONTENT_FOR_LAYOUT_PATTERN = /\{\{\s*content_for_layout\s*\}\}/
enum :locale, LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h, prefix: true
@@ -34,6 +35,7 @@ class EmailTemplate < ApplicationRecord
if: :installation_scoped?
validates :name, uniqueness: { scope: %i[account_id template_type locale], conditions: -> { where(inbox_id: nil) } }, if: :account_scoped?
validates :name, uniqueness: { scope: %i[inbox_id template_type locale] }, if: :inbox_scoped?
validates :body, length: { maximum: MAX_BODY_LENGTH }
validate :validate_inbox_account
validate :validate_liquid_body
validate :validate_layout_slot, if: :layout?
@@ -85,6 +85,20 @@ RSpec.describe 'Branded Email Layout API', type: :request do
expect(response.parsed_body['branded_email_layout']).to eq(layout)
end
it 'updates account-scoped branded email layouts up to 100 KiB' do
account.enable_features!(:branded_email_templates)
slot = '{{ content_for_layout }}'
large_layout = "#{'a' * (100.kilobytes - slot.length)}#{slot}"
patch "/api/v1/accounts/#{account.id}/branded_email_layout",
headers: admin.create_new_auth_token,
params: { branded_email_layout: large_layout },
as: :json
expect(response).to have_http_status(:success)
expect(EmailTemplate.account_branded_layout_template_for(account).body.length).to eq(100.kilobytes)
end
it 'clears account-scoped branded email layout when blank value is passed' do
account.enable_features!(:branded_email_templates)
create(:email_template, :layout, account: account, body: layout)
@@ -636,6 +636,22 @@ RSpec.describe 'Inboxes API', type: :request do
expect(response.parsed_body['branded_email_layout']).to eq(layout)
end
it 'rejects branded email layouts larger than 256 KiB' do
account.enable_features!(:branded_email_templates)
email_channel = create(:channel_email, account: account)
email_inbox = create(:inbox, channel: email_channel, account: account)
slot = '{{ content_for_layout }}'
large_layout = "#{'a' * (EmailTemplate::MAX_BODY_LENGTH - slot.length + 1)}#{slot}"
patch "/api/v1/accounts/#{account.id}/inboxes/#{email_inbox.id}",
headers: admin.create_new_auth_token,
params: { branded_email_layout: large_layout },
as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(response.parsed_body['error']).to include('is too long (maximum is 262144 characters)')
end
it 'rolls back branded email layout when inbox update fails' do
account.enable_features!(:branded_email_templates)
email_channel = create(:channel_email, account: account)
+17
View File
@@ -68,6 +68,23 @@ RSpec.describe EmailTemplate do
expect(template.errors[:body]).to include('must include {{ content_for_layout }}')
end
it 'allows email templates up to 262,144 characters' do
slot = '{{ content_for_layout }}'
body = "#{'a' * (described_class::MAX_BODY_LENGTH - slot.length)}#{slot}"
template = build(:email_template, :layout, account: create(:account), body: body)
expect(template).to be_valid
end
it 'rejects email templates larger than 262,144 characters' do
slot = '{{ content_for_layout }}'
body = "#{'a' * (described_class::MAX_BODY_LENGTH - slot.length + 1)}#{slot}"
template = build(:email_template, :layout, account: create(:account), body: body)
expect(template).not_to be_valid
expect(template.errors[:body]).to include('is too long (maximum is 262144 characters)')
end
it 'validates liquid syntax' do
template = build(:email_template, body: '{{ broken ')
@@ -4,5 +4,6 @@ properties:
type:
- string
- 'null'
description: Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.
maxLength: 262144
description: Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.
example: '<html><body>{{ content_for_layout }}</body></html>'
@@ -113,8 +113,9 @@ properties:
type:
- string
- 'null'
maxLength: 262144
description: |
Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.
Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.
Available for: `Email`
example: '<html><body>{{ content_for_layout }}</body></html>'
+4 -2
View File
@@ -12804,7 +12804,8 @@
"string",
"null"
],
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.\n\nAvailable for: `Email`\n",
"maxLength": 262144,
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.\n\nAvailable for: `Email`\n",
"example": "<html><body>{{ content_for_layout }}</body></html>"
},
"channel": {
@@ -12842,7 +12843,8 @@
"string",
"null"
],
"description": "Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.",
"maxLength": 262144,
"description": "Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.",
"example": "<html><body>{{ content_for_layout }}</body></html>"
}
}
+4 -2
View File
@@ -11311,7 +11311,8 @@
"string",
"null"
],
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.\n\nAvailable for: `Email`\n",
"maxLength": 262144,
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.\n\nAvailable for: `Email`\n",
"example": "<html><body>{{ content_for_layout }}</body></html>"
},
"channel": {
@@ -11349,7 +11350,8 @@
"string",
"null"
],
"description": "Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.",
"maxLength": 262144,
"description": "Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.",
"example": "<html><body>{{ content_for_layout }}</body></html>"
}
}
+4 -2
View File
@@ -4119,7 +4119,8 @@
"string",
"null"
],
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.\n\nAvailable for: `Email`\n",
"maxLength": 262144,
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.\n\nAvailable for: `Email`\n",
"example": "<html><body>{{ content_for_layout }}</body></html>"
},
"channel": {
@@ -4157,7 +4158,8 @@
"string",
"null"
],
"description": "Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.",
"maxLength": 262144,
"description": "Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.",
"example": "<html><body>{{ content_for_layout }}</body></html>"
}
}
+4 -2
View File
@@ -3534,7 +3534,8 @@
"string",
"null"
],
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.\n\nAvailable for: `Email`\n",
"maxLength": 262144,
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.\n\nAvailable for: `Email`\n",
"example": "<html><body>{{ content_for_layout }}</body></html>"
},
"channel": {
@@ -3572,7 +3573,8 @@
"string",
"null"
],
"description": "Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.",
"maxLength": 262144,
"description": "Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.",
"example": "<html><body>{{ content_for_layout }}</body></html>"
}
}
+4 -2
View File
@@ -4295,7 +4295,8 @@
"string",
"null"
],
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}`.\n\nAvailable for: `Email`\n",
"maxLength": 262144,
"description": "Liquid HTML layout for outbound email replies. Must include `{{ content_for_layout }}` and be 262,144 characters or shorter.\n\nAvailable for: `Email`\n",
"example": "<html><body>{{ content_for_layout }}</body></html>"
},
"channel": {
@@ -4333,7 +4334,8 @@
"string",
"null"
],
"description": "Account-scoped Liquid HTML layout for branded email replies. Blank or null removes the account override.",
"maxLength": 262144,
"description": "Account-scoped Liquid HTML layout for branded email replies. Maximum length is 262,144 characters. Blank or null removes the account override.",
"example": "<html><body>{{ content_for_layout }}</body></html>"
}
}