feat(cms): white-label email templates and API doc config (#2)
All checks were successful
Build and publish / build (push) Successful in 7m11s

## Summary

- Replace all hardcoded muellerprints brand identity in email templates with template variables sourced from env vars
- Inject shop context into both `sendInvoice` and `sendDeliveryNote` template calls in `order.ts`
- Make API documentation title/description env-var driven

## New env vars (all optional with safe defaults)

| Var | Default | Used in |
|-----|---------|---------|
| `SHOP_NAME` | `"Shop"` | Email subject, body, footer |
| `SHOP_CONTACT_NAME` | `""` | Email footer |
| `SHOP_ADDRESS` | `""` | Email footer |
| `SHOP_PHONE` | `""` | Email footer |
| `SHOP_EMAIL` | `ADMIN_EMAIL_ADDRESS` | Email footer |
| `SHOP_LOGO_URL` | `""` | Email header logo |
| `SHOP_SECONDARY_LOGO_URL` | `""` | Email footer logo |
| `API_TITLE` | `"Paperwork API"` | Swagger/OpenAPI title |
| `API_DESCRIPTION` | `"Paperwork API"` | Swagger/OpenAPI description |

## Test plan

- [ ] Set `SHOP_NAME=TestShop` and trigger invoice send → confirm subject and body use `TestShop`
- [ ] Leave `SHOP_NAME` unset → confirm default `"Shop"` appears
- [ ] Set `SHOP_LOGO_URL` → confirm logo renders in email header; unset → confirm no broken `<img>` tag
- [ ] Check Strapi admin `/documentation` with `API_TITLE=MyAPI` env var

Closes #1

Reviewed-on: #2
Co-authored-by: Michael Czechowski <mail@dailysh.it>
Co-committed-by: Michael Czechowski <mail@dailysh.it>
This commit is contained in:
2026-04-29 20:13:23 +02:00
committed by Michael Czechowski
parent a716f52dd4
commit defd078e4b
6 changed files with 74 additions and 49 deletions

View File

@@ -2,7 +2,26 @@
All notable changes to libreshop/cms are documented here. All notable changes to libreshop/cms are documented here.
## Unreleased ## [0.1.1] - 2026-04-29
### Changed
- White-label email templates: replace all hardcoded muellerprints company
identity (name, contact, address, phone, email, logo URLs) with
`<%= shopName %>` / `<%= shopContactName %>` / `<%= shopAddress %>` /
`<%= shopPhone %>` / `<%= shopEmail %>` / `<%= shopLogoUrl %>` /
`<%= shopSecondaryLogoUrl %>` template variables, sourced from env vars
`SHOP_NAME`, `SHOP_CONTACT_NAME`, `SHOP_ADDRESS`, `SHOP_PHONE`,
`SHOP_EMAIL`, `SHOP_LOGO_URL`, `SHOP_SECONDARY_LOGO_URL`.
- `config/constants.ts`: export shop brand constants from env.
- `src/api/order/services/order.ts`: inject shop context into template calls.
- `config/plugins.ts`: API doc title/description driven by `API_TITLE` /
`API_DESCRIPTION` env vars (defaults: `"Paperwork API"`).
- `src/extensions/documentation/config/settings.json`: neutral placeholder.
Closes #1
## [0.1.0] - 2026-04-29
- Extracted from `mp/cms/` (2026-04-29). The component history before - Extracted from `mp/cms/` (2026-04-29). The component history before
the extraction lives in the `muellerprints` repository. the extraction lives in the `muellerprints` repository.

View File

@@ -10,6 +10,14 @@ export const paypalEnvironment = process.env.PAYPAL_ENVIRONMENT! === "production
export const adminEmail = process.env.ADMIN_EMAIL_ADDRESS!; export const adminEmail = process.env.ADMIN_EMAIL_ADDRESS!;
export const shopName = process.env.SHOP_NAME ?? "Shop";
export const shopContactName = process.env.SHOP_CONTACT_NAME ?? "";
export const shopAddress = process.env.SHOP_ADDRESS ?? "";
export const shopPhone = process.env.SHOP_PHONE ?? "";
export const shopEmail = process.env.SHOP_EMAIL ?? process.env.ADMIN_EMAIL_ADDRESS ?? "";
export const shopLogoUrl = process.env.SHOP_LOGO_URL ?? "";
export const shopSecondaryLogoUrl = process.env.SHOP_SECONDARY_LOGO_URL ?? "";
// TODO: Should be retrieved from DepotApi // TODO: Should be retrieved from DepotApi
export const vatIncludedDecimal = 1.19; export const vatIncludedDecimal = 1.19;
// TODO: Should be retrieved from DepotApi // TODO: Should be retrieved from DepotApi

View File

@@ -10,15 +10,11 @@ export default ({ env }) => ({
openapi: "3.0.1", openapi: "3.0.1",
info: { info: {
version: "1.0.0", version: "1.0.0",
title: "MUELLERPTINTS. Paperwork", title: env("API_TITLE", "Paperwork API"),
description: "API Documentation for MUELLERPRINTS. Paperwork", description: env("API_DESCRIPTION", "Paperwork API"),
termsOfService: false, termsOfService: false,
contact: { contact: false,
name: "Michael W. Czechowski", license: false,
email: "mail@dailysh.it",
url: "https://dailysh.it"
},
license: "Copyright (C) 2024 Michael W. Czechowski",
externalDocs: false externalDocs: false
}, },
"x-strapi-config": { "x-strapi-config": {

View File

@@ -3,7 +3,7 @@ import fs from "fs";
import { factories } from "@strapi/strapi"; import { factories } from "@strapi/strapi";
import { sanitize } from "@strapi/utils"; import { sanitize } from "@strapi/utils";
import { ID } from "@strapi/database/dist/types"; import { ID } from "@strapi/database/dist/types";
import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail } from "../../../../config/constants"; import { vatDecimal, vatIncludedDecimal, baseUrl, adminEmail, shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl } from "../../../../config/constants";
import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product"; import { calculateTotalProductPrice, productDefaultParams } from "../../product/services/product";
import { CartProduct, Order, PdfBody } from "../../../../types"; import { CartProduct, Order, PdfBody } from "../../../../types";
import { pdfApi } from "../../../services/PdfApi"; import { pdfApi } from "../../../services/PdfApi";
@@ -81,12 +81,13 @@ export default factories.createCoreService("api::order.order", ({ strapi }) => (
strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`); strapi.log.info(`app:i:order-service: - Sending invoice for order ${uuid}`);
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>; const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl };
try { try {
const emailConfig: MessageBody = { const emailConfig: MessageBody = {
to_email: oderUnsafe.email, to_email: oderUnsafe.email,
subject: template(invoiceEmailTemplate.subject)(oderUnsafe), subject: template(invoiceEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }),
message: template(invoiceEmailTemplate.text)({ baseUrl, ...oderUnsafe }), message: template(invoiceEmailTemplate.text)({ baseUrl, ...shopContext, ...oderUnsafe }),
html: template(invoiceEmailTemplate.html)({ baseUrl, ...oderUnsafe }) html: template(invoiceEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe })
}; };
strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`); strapi.log.debug(`app:d:order-service: - Email config ${emailConfig.html}`);
@@ -118,12 +119,13 @@ export default factories.createCoreService("api::order.order", ({ strapi }) => (
sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => { sendDeliveryNote: async (uuid: string, blob?: ArrayBuffer) => {
strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`); strapi.log.info(`app:i:order-service: - Sending delivery note for order ${uuid}`);
const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>; const oderUnsafe = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Partial<Order>;
const shopContext = { shopName, shopContactName, shopAddress, shopPhone, shopEmail, shopLogoUrl, shopSecondaryLogoUrl };
try { try {
const emailConfig: MessageBody = { const emailConfig: MessageBody = {
to_email: oderUnsafe.email, to_email: oderUnsafe.email,
subject: template(deliveryNoteEmailTemplate.subject)(oderUnsafe), subject: template(deliveryNoteEmailTemplate.subject)({ ...shopContext, ...oderUnsafe }),
message: template(deliveryNoteEmailTemplate.text)(oderUnsafe), message: template(deliveryNoteEmailTemplate.text)({ ...shopContext, ...oderUnsafe }),
html: template(deliveryNoteEmailTemplate.html)({ ...oderUnsafe, baseUrl }) html: template(deliveryNoteEmailTemplate.html)({ baseUrl, ...shopContext, ...oderUnsafe })
}; };
await mailApi.sendTextMessage(emailConfig); await mailApi.sendTextMessage(emailConfig);
strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`); strapi.log.info(`app:i:order-service: ✔ Sending delivery note for order ${uuid}`);

View File

@@ -2,8 +2,8 @@
"openapi": "3.0.1", "openapi": "3.0.1",
"info": { "info": {
"version": "1.0.0", "version": "1.0.0",
"title": "MUELLERPTINTS. Paperwork", "title": "Paperwork API",
"description": "API Documentation for MUELLERPRINTS. Paperwork", "description": "Paperwork API",
"termsOfService": false, "termsOfService": false,
"contact": false, "contact": false,
"license": false "license": false

View File

@@ -1,5 +1,5 @@
export const invoiceEmailTemplate = { export const invoiceEmailTemplate = {
subject: "MUELLERPRINTS Rechnungsnummer <%= invoiceNumber %>", subject: "<%= shopName %> Rechnungsnummer <%= invoiceNumber %>",
text: `Rechnungsnummer: <%= invoiceNumber %> text: `Rechnungsnummer: <%= invoiceNumber %>
Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString("de-DE") %> Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString("de-DE") %>
@@ -7,7 +7,7 @@ Ausstellungsdatum: <%= new Date(acceptedTermsAndConditionsAt).toLocaleDateString
Hallo <%= invoiceAddressStructured.givenName %>, Hallo <%= invoiceAddressStructured.givenName %>,
vielen Dank für deinen Einkauf bei MUELLERPRINTS. vielen Dank für deinen Einkauf bei <%= shopName %>.
Deine Bestellnummer: <%= id %> Deine Bestellnummer: <%= id %>
Zur Bestellübersicht: <%= baseUrl %>/checkout/result/<%= uuid %> Zur Bestellübersicht: <%= baseUrl %>/checkout/result/<%= uuid %>
@@ -49,14 +49,14 @@ inkl. MwSt. (19%): <%= VAT.toFixed(2) %> EUR
Deine Rechnung kannst du jederzeit über diesen Link herunterladen: <%= baseUrl %><%= invoice.url %> Deine Rechnung kannst du jederzeit über diesen Link herunterladen: <%= baseUrl %><%= invoice.url %>
<% } %> <% } %>
Vielen Dank, dass du dich für MUELLERPRINTS entschieden hast. Vielen Dank, dass du dich für <%= shopName %> entschieden hast.
------------------ ------------------
MUELLERPRINTS <%= shopName %>
Max Müller <%= shopContactName %>
Rotenbergstraße 39, 70190 Stuttgart <%= shopAddress %>
T +49 (0)711/262 49 64 <%= shopPhone ? "T " + shopPhone : "" %>
paperwork@muellerprints.de`, <%= shopEmail %>`,
html: `<!DOCTYPE html> html: `<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -74,7 +74,7 @@ paperwork@muellerprints.de`,
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background-color: #E31E26; padding: 30px 40px;"> <td style="background-color: #E31E26; padding: 30px 40px;">
<img src="https://muellerprints-paperwork.com/paperwork-logo.png" alt="MUELLERPRINTS PAPERWORK" style="max-width: 200px; height: auto;"> <% if (shopLogoUrl) { %><img src="<%= shopLogoUrl %>" alt="<%= shopName %>" style="max-width: 200px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
@@ -83,7 +83,7 @@ paperwork@muellerprints.de`,
<td style="padding: 40px;"> <td style="padding: 40px;">
<!-- Greeting --> <!-- Greeting -->
<h1 style="margin: 0 0 20px; color: #333; font-size: 24px; font-weight: normal;"> <h1 style="margin: 0 0 20px; color: #333; font-size: 24px; font-weight: normal;">
Vielen Dank für deinen Einkauf bei MUELLERPRINTS. Vielen Dank für deinen Einkauf bei <%= shopName %>.
</h1> </h1>
<!-- Order Info --> <!-- Order Info -->
@@ -197,7 +197,7 @@ paperwork@muellerprints.de`,
<% } %> <% } %>
<p style="color: #666; font-size: 14px; margin: 20px 0;"> <p style="color: #666; font-size: 14px; margin: 20px 0;">
Vielen Dank, dass du dich für MUELLERPRINTS entschieden hast. Vielen Dank, dass du dich für <%= shopName %> entschieden hast.
</p> </p>
</td> </td>
</tr> </tr>
@@ -209,15 +209,15 @@ paperwork@muellerprints.de`,
<tr> <tr>
<td style="color: #666; font-size: 12px; line-height: 1.5;"> <td style="color: #666; font-size: 12px; line-height: 1.5;">
<p style="margin: 0 0 10px;"> <p style="margin: 0 0 10px;">
MUELLERPRINTS<br> <%= shopName %><br>
Max Müller<br> <% if (shopContactName) { %><%= shopContactName %><br><% } %>
Rotenbergstraße 39, 70190 Stuttgart<br> <% if (shopAddress) { %><%= shopAddress %><br><% } %>
T +49 (0)711/262 49 64<br> <% if (shopPhone) { %>T <%= shopPhone %><br><% } %>
paperwork@muellerprints.de <% if (shopEmail) { %><%= shopEmail %><% } %>
</p> </p>
</td> </td>
<td style="text-align: right; vertical-align: bottom;"> <td style="text-align: right; vertical-align: bottom;">
<img src="https://www.muellerprints.de/images/digi_logo.png" alt="MUELLERPRINTS" style="max-width: 100px; height: auto;"> <% if (shopSecondaryLogoUrl) { %><img src="<%= shopSecondaryLogoUrl %>" alt="<%= shopName %>" style="max-width: 100px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
</table> </table>
@@ -243,7 +243,7 @@ paperwork@muellerprints.de`,
}; };
export const deliveryNoteEmailTemplate = { export const deliveryNoteEmailTemplate = {
subject: "Your Delivery Note <%= deliveryNoteNumber %> from MUELLERPRINTS", subject: "Your Delivery Note <%= deliveryNoteNumber %> from <%= shopName %>",
text: `Dear Customer, text: `Dear Customer,
Thank you for your Your delivery note <%= deliveryNoteNumber %> is attached to this email. Thank you for your Your delivery note <%= deliveryNoteNumber %> is attached to this email.
@@ -272,14 +272,14 @@ Delivery Address:
If you have any questions about your delivery, please don't hesitate to contact us. If you have any questions about your delivery, please don't hesitate to contact us.
Best regards, Best regards,
MUELLERPRINTS PAPERWORK <%= shopName %>
------------------ ------------------
MUELLERPRINTS PAPERWORK <%= shopName %>
Max Müller <%= shopContactName %>
Rotenbergstraße 39, 70190 Stuttgart <%= shopAddress %>
T +49 (0)711/262 49 64 <%= shopPhone ? "T " + shopPhone : "" %>
paperwork@muellerprints.de`, <%= shopEmail %>`,
html: `<!DOCTYPE html> html: `<!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
@@ -295,7 +295,7 @@ paperwork@muellerprints.de`,
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background-color: #E31E26; padding: 30px 40px;"> <td style="background-color: #E31E26; padding: 30px 40px;">
<img src="https://muellerprints-paperwork.com/paperwork-logo.png" alt="MUELLERPRINTS PAPERWORK" style="max-width: 200px; height: auto;"> <% if (shopLogoUrl) { %><img src="<%= shopLogoUrl %>" alt="<%= shopName %>" style="max-width: 200px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
@@ -407,15 +407,15 @@ paperwork@muellerprints.de`,
<tr> <tr>
<td style="color: #666; font-size: 12px; line-height: 1.5;"> <td style="color: #666; font-size: 12px; line-height: 1.5;">
<p style="margin: 0 0 10px;"> <p style="margin: 0 0 10px;">
MUELLERPRINTS PAPERWORK<br> <%= shopName %><br>
Max Müller<br> <% if (shopContactName) { %><%= shopContactName %><br><% } %>
Rotenbergstraße 39, 70190 Stuttgart<br> <% if (shopAddress) { %><%= shopAddress %><br><% } %>
T +49 (0)711/262 49 64<br> <% if (shopPhone) { %>T <%= shopPhone %><br><% } %>
paperwork@muellerprints.de <% if (shopEmail) { %><%= shopEmail %><% } %>
</p> </p>
</td> </td>
<td style="text-align: right; vertical-align: bottom;"> <td style="text-align: right; vertical-align: bottom;">
<img src="https://www.muellerprints.de/images/digi_logo.png" alt="MUELLERPRINTS" style="max-width: 100px; height: auto;"> <% if (shopSecondaryLogoUrl) { %><img src="<%= shopSecondaryLogoUrl %>" alt="<%= shopName %>" style="max-width: 100px; height: auto;"><% } %>
</td> </td>
</tr> </tr>
</table> </table>