Skip to content

#86 - Add branding support to email templates - #87

Open
reshmabidikar wants to merge 3 commits into
killbill:masterfrom
reshmabidikar:work-for-86
Open

#86 - Add branding support to email templates#87
reshmabidikar wants to merge 3 commits into
killbill:masterfrom
reshmabidikar:work-for-86

Conversation

@reshmabidikar

@reshmabidikar reshmabidikar commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Work for #86:

  • Branding support added to email templates
  • Fast tests modified and re-enabled (they were disabled during the 0.24.x work)
  • Additional fast test added for branding feature

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds shared-tenant branding support (logo, company info, brand colors) to the email notifications plugin by introducing branding loaders and Mustache partial templates, and updates/enables fast tests to validate rendering with tenant-provided branding data.

Changes:

  • Introduces EmailBrandingLoader and associated data classes/keys to fetch and merge branding data from tenant KV.
  • Adds Mustache partials (tenantStyleInfo, tenantLogoInfo, tenantCompanyInfo) and a new invoice template variant consuming them.
  • Refactors and re-enables fast template-rendering tests, adding a new test covering tenant branding variables.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/test/resources/org/killbill/billing/plugin/notification/templates/logoInfo.json Test logo payload used to validate tenant logo rendering.
src/test/resources/org/killbill/billing/plugin/notification/templates/InvoiceCreation-new-fields.mustache New invoice template variant using branding partials.
src/test/resources/org/killbill/billing/plugin/notification/templates/companyInfo.json Test company info JSON used by branding loader tests.
src/test/resources/org/killbill/billing/plugin/notification/templates/brandInfo.json Test brand color JSON used by branding loader tests.
src/test/java/org/killbill/billing/plugin/notification/generator/TestTemplateRenderer.java Re-enables/refactors fast tests and adds branding-specific test coverage.
src/main/resources/org/killbill/billing/plugin/notification/templates/tenantStyleInfo.mustache Partial defining CSS variables from tenant brand info.
src/main/resources/org/killbill/billing/plugin/notification/templates/tenantLogoInfo.mustache Partial to render tenant logo in templates.
src/main/resources/org/killbill/billing/plugin/notification/templates/tenantCompanyInfo.mustache Partial to render tenant company information.
src/main/java/org/killbill/billing/plugin/notification/templates/MustacheTemplateEngine.java Adds a shared Mustache compiler configured to resolve partials from classpath resources.
src/main/java/org/killbill/billing/plugin/notification/generator/TemplateRenderer.java Injects company/logo/brand objects into the Mustache rendering data model.
src/main/java/org/killbill/billing/plugin/notification/email/branding/LogoInfo.java New POJO for logo data.
src/main/java/org/killbill/billing/plugin/notification/email/branding/EmailBrandingLoader.java New loader to retrieve and merge branding data from tenant KV.
src/main/java/org/killbill/billing/plugin/notification/email/branding/EmailBrandingKey.java New enum defining tenant KV keys for branding.
src/main/java/org/killbill/billing/plugin/notification/email/branding/CompanyInfo.java New POJO for company info data.
src/main/java/org/killbill/billing/plugin/notification/email/branding/BrandInfo.java New POJO for brand color data.
.idea/misc.xml Updates IDE metadata (project JDK and external storage config).
Files not reviewed (1)
  • .idea/misc.xml: Generated file
Suppressed comments (2)

src/main/java/org/killbill/billing/plugin/notification/email/branding/EmailBrandingLoader.java:75

  • Issue #86 calls for shared tenant branding to take precedence. getEmailTemplateLogoInfo currently prefers EMAIL_TEMPLATE_LOGO_INFO over LOGO_INFO, which makes template-specific branding override shared branding.
    public LogoInfo getEmailTemplateLogoInfo(final TenantContext context) {
        String value = getValue(EmailBrandingKey.EMAIL_TEMPLATE_LOGO_INFO, "email template logo info", context);
        if (value == null) {
            value = getValue(EmailBrandingKey.LOGO_INFO, "company info", context);
        }
        return deserializeJson(value, LogoInfo.class, new LogoInfo(DEFAULT_PROPERTIES.get("logo")));

src/main/java/org/killbill/billing/plugin/notification/email/branding/EmailBrandingLoader.java:82

  • Brand color precedence is currently template-specific first (EMAIL_TEMPLATE_BRAND_INFO overrides BRAND_INFO). Per issue #86, shared tenant branding should be used when available, with template-specific values only as fallback.
    public BrandInfo getEmailTemplateBrandInfo(final TenantContext context) {
        final BrandInfo invoiceTemplate = loadBrandInfo(EmailBrandingKey.EMAIL_TEMPLATE_BRAND_INFO, "email template brand info", context);
        final BrandInfo global = loadBrandInfo(EmailBrandingKey.BRAND_INFO, "brand info", context);
        return mergeBrandInfo(invoiceTemplate, global);
    }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +134 to +138
if (values.size() > 1) {
throw new IllegalStateException(String.format(
"Unexpected number of values %d for %s and tenant %d",
values.size(), msg, tenantContext.getTenantId()));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e2489fd

Comment on lines +62 to +68
public CompanyInfo getEmailTemplateCompanyInfo(final TenantContext context) {
String value = getValue(EmailBrandingKey.EMAIL_TEMPLATE_COMPANY_INFO, "email template company info", context);
if (value == null) {
value = getValue(EmailBrandingKey.COMPANY_INFO, "company info", context);
}
return deserializeJson(value, CompanyInfo.class, new CompanyInfo(DEFAULT_PROPERTIES.get("companyName"), DEFAULT_PROPERTIES.get("companyAddress"), DEFAULT_PROPERTIES.get("companyCityProvincePostalCode"), DEFAULT_PROPERTIES.get("companyCountry"), DEFAULT_PROPERTIES.get("companyUrl")));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The code behavior is correct, ignoring this one.

Comment on lines +84 to +96
private String getValue(final EmailBrandingKey key,
final String description,
final TenantContext tenantContext) {
final List<String> values;
try {
values = tenantApi.getTenantValuesForKey(key.toString(), tenantContext);
return getUniqueValue(values, description, tenantContext);
} catch (final TenantApiException e) {
//TODO_86 - add a log statement

}
return null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e2489fd

@@ -0,0 +1 @@
{{#logo}}<img src="{{{logo}}}" style="width:100%; max-width:300px;">{{/logo}} No newline at end of file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e2489fd

Comment thread .idea/misc.xml
Comment on lines 2 to 6
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="Osmorc" url="file://$PROJECT_DIR$" />
</component>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e2489fd

Comment thread .idea/misc.xml Outdated
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="11" project-jdk-type="JavaSDK" />

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e2489fd

@sbrossie sbrossie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I only looked at the diff so I may be missing the big picture: Does this PR contain everything we need? Could you add some screenshots for the default branding, logo we provide?

EMAIL_TEMPLATE_COMPANY_INFO,
EMAIL_TEMPLATE_BRAND_INFO,
EMAIL_TEMPLATE_LOGO_INFO,
EMAIL_TEMPLATE_WITH_BRANDING

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need this key - where is it used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not required, was a copy paste error, I have removed it in 043fbe2.

@@ -0,0 +1,219 @@
<!doctype html>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we need to modify the other template types - seems like from a symmetry aspect, if we modify InvoiceCreation, any other type should also be modified?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This template is created just for unit tests. See this test. I did not write unit tests for all the template types and so did not add email templates for the other email types.

@reshmabidikar

Copy link
Copy Markdown
Contributor Author

I only looked at the diff so I may be missing the big picture: Does this PR contain everything we need? Could you add some screenshots for the default branding, logo we provide?

Here are some screenshots.

Default template (without the new branding variables):

Default-template-no-branding-key-values

Custom template (with new branding variables), default values for the company/brand/logo information (let me know if you think these default values should be changed, they are defined here in the code):

template-with-new-variables-default-key-valuea

Custom template (with new branding variables), custom values for the company/brand/logo information:

template-with-new-variables-custom-key-values

@sbrossie sbrossie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Were you able to deploy on snapshot - or simply make a release - on our QA env to test if this provides the full story with Aviate - i.e. Email Configuration?

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.

3 participants