---
phase: 51-email-html-layout
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260328_000001_add_html_layout_to_email_mailboxes.sql
- src/Modules/Settings/EmailMailboxController.php
- src/Modules/Settings/EmailMailboxRepository.php
- resources/views/settings/email-mailboxes.php
- src/Modules/Email/EmailSendingService.php
autonomous: true
---
## Goal
Rozbudowa modulu e-mail o HTML layout: header i footer konfigurowane na poziomie skrzynki pocztowej, content z szablonu. Finalna wiadomosc = header + content + footer. Edytor ograniczony do email-safe HTML.
## Purpose
Umozliwienie uzytkownikowi stworzenia spojnego brandingu e-mail (naglowek z logo/nazwa firmy, stopka z danymi kontaktowymi) bez powielania tresci w kazdym szablonie.
## Output
- Migracja DB: kolumny `header_html`, `footer_html` w `email_mailboxes`
- UI skrzynek: dwa edytory Quill (header/footer) z toolbar email-safe
- Kompozycja e-mail: header + body + footer w EmailSendingService i preview
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/EmailMailboxController.php
@src/Modules/Settings/EmailMailboxRepository.php
@resources/views/settings/email-mailboxes.php
@src/Modules/Email/EmailSendingService.php
@resources/views/settings/email-templates.php (reference — Quill config)
## Required Skills (from SPECIAL-FLOWS.md)
No specialized flows configured as required for this work type.
## AC-1: Kolumny DB header_html i footer_html
```gherkin
Given tabela email_mailboxes istnieje
When migracja zostanie wykonana
Then tabela zawiera kolumny header_html TEXT NULL i footer_html TEXT NULL
And istniejace rekordy maja NULL w obu kolumnach (brak breaking change)
```
## AC-2: Edycja header/footer w formularzu skrzynki
```gherkin
Given uzytkownik otwiera formularz edycji skrzynki pocztowej
When widzi sekcje "Szablon wiadomosci" pod ustawieniami SMTP
Then sa dwa edytory Quill: "Naglowek (header)" i "Stopka (footer)"
And toolbar kazdego edytora ogranicza sie do: bold, italic, underline, link, kolor tekstu, kolor tla, wyrownanie, listy, naglowki (h1-h3), obraz (inline base64)
And tresc edytorow jest zapisywana do DB przy submit formularza
```
## AC-3: Kompozycja e-mail header + content + footer
```gherkin
Given skrzynka ma ustawiony header_html i footer_html
And szablon ma body_html
When e-mail jest wysylany lub podgladany (preview)
Then tresc wiadomosci = header_html + body_html (resolved) + footer_html
And header i footer rowniez przechodza przez variable resolver
```
## AC-4: E-mail bez header/footer
```gherkin
Given skrzynka ma puste (NULL) header_html i/lub footer_html
When e-mail jest wysylany
Then tresc wiadomosci zawiera tylko body_html (bez pustych sekcji)
```
Task 1: Migracja DB + Repository + Controller
database/migrations/20260328_000001_add_html_layout_to_email_mailboxes.sql,
src/Modules/Settings/EmailMailboxRepository.php,
src/Modules/Settings/EmailMailboxController.php
1. Utworzyc migracje SQL:
```sql
ALTER TABLE email_mailboxes
ADD COLUMN header_html TEXT NULL AFTER sender_name,
ADD COLUMN footer_html TEXT NULL AFTER header_html;
```
2. EmailMailboxRepository:
- `save()`: dodac `header_html` i `footer_html` do INSERT/UPDATE
- `findById()`: upewnic sie ze zwraca te kolumny (juz zwraca SELECT * wiec OK)
- `listAll()`: bez zmian (nie potrzebuje HTML w liscie)
3. EmailMailboxController:
- `save()`: pobrac `header_html` i `footer_html` z POST body
- Nie escapowac HTML (to jest tresc edytora WYSIWYG, jak body_html w szablonach)
- Przekazac do repozytorium w tablicy save data
- Migracja wykonuje sie bez bledow
- DESCRIBE email_mailboxes pokazuje header_html i footer_html jako TEXT NULL
- Zapis i odczyt skrzynki z header/footer dziala poprawnie
AC-1 satisfied: kolumny istnieja i sa zapisywane/odczytywane
Task 2: UI edytorow header/footer w formularzu skrzynki
resources/views/settings/email-mailboxes.php
1. Po sekcji "Ustawienia SMTP" dodac nowa sekcje "Szablon wiadomosci" z dwoma edytorami Quill
2. Kazdy edytor (header, footer):
- Label: "Naglowek (header)" / "Stopka (footer)"
- Div z id `js-header-editor` / `js-footer-editor` (kontener Quill)
- Hidden input `header_html` / `footer_html` (sync przy submit)
- Podpowiedz: "Opcjonalnie. Bedzie dolaczany do kazdego e-maila wysylanego z tej skrzynki."
3. Toolbar edytora — TYLKO email-safe opcje:
```javascript
[
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline'],
[{ color: [] }, { background: [] }],
[{ align: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image'],
['clean']
]
```
- Image: Quill domyslnie wstawia jako base64 inline — to jest email-safe
- Brak: strike, blockquote, code-block, video, indent (slabo obslugiwane w klientach pocztowych)
4. Zaladowac Quill CSS/JS z CDN (ten sam co w email-templates: 2.0.3)
5. Na submit formularza: sync innerHTML z edytorow do hidden inputs
6. Przy edycji istniejacego rekordu: zaladowac HTML do edytorow przez `quill.root.innerHTML = ...`
7. Edytory powinny miec mniejsza domyslna wysokosc niz edytor szablonu (np. min-height: 80px)
- Otworz /settings/email-mailboxes — formularz pokazuje dwa edytory
- Wpisz tresc w header/footer, zapisz — dane sa w DB
- Edytuj skrzynke — header/footer sa zaladowane do edytorow
- Toolbar nie zawiera opcji niebezpiecznych dla e-mail (video, code-block)
AC-2 satisfied: edytory sa dostepne, ograniczone do email-safe, i zapisuja dane
Task 3: Kompozycja e-mail w EmailSendingService
src/Modules/Email/EmailSendingService.php
1. W metodzie `send()` po rozwiazaniu zmiennych w body:
- Pobrac header_html i footer_html z resolved mailbox
- Przepuscic je przez variableResolver.resolve() (aby zmienne dzialaly tez w header/footer)
- Zlozyc finalBody: header_html + resolvedBody + footer_html
- Jezeli header_html lub footer_html sa NULL/puste — pominac (bez pustych divow)
- Uzyc finalBody zamiast resolvedBody w sendViaSMTP i logEmail
2. W metodzie `preview()`:
- Analogicznie: pobrac mailbox dla szablonu, zlozyc header + body + footer
- Aby preview dzialal, potrzebujemy mailbox — uzyc resolveMailbox() (juz istnieje)
- Jezeli mailbox nie znaleziony — pokazac sam body (bez header/footer)
3. Metoda `resolveMailbox()` jest private — juz zwraca pelne dane z findById() wlacznie z header_html/footer_html
4. Nowa prywatna metoda `composeBody(string $resolvedBody, ?array $mailbox, array $variableMap): string`:
- Wydzielic logike kompozycji do reusable metody
- Uzyc w send() i preview()
- Wyslij e-mail ze skrzynka z ustawionym header/footer — e-mail zawiera header + body + footer
- Wyslij e-mail ze skrzynka BEZ header/footer — e-mail zawiera tylko body
- Preview pokazuje zlozony wynik header + body + footer
- Zmienne w header/footer sa rozwiazywane (np. {{firma.nazwa}})
AC-3 i AC-4 satisfied: kompozycja dziala z i bez header/footer
## DO NOT CHANGE
- resources/views/settings/email-templates.php (edytor szablonow — bez zmian)
- src/Modules/Email/VariableResolver.php (resolver zmiennych — bez zmian)
- src/Modules/Email/AttachmentGenerator.php
- database/migrations/ (istniejace migracje)
- Struktura tabeli email_templates (body_html pozostaje jak jest)
## SCOPE LIMITS
- Brak edytora MJML / dedykowanego email buildera — Quill z ograniczonym toolbar wystarcza
- Brak podgladu header/footer w formularzu skrzynki (preview jest w szablonach)
- Brak importu gotowych szablonow HTML
- Zmienne w header/footer dzialaja, ale panel zmiennych NIE jest dodawany do formularza skrzynki (header/footer to zwykle statyczny branding)
Before declaring plan complete:
- [ ] Migracja wykonana, kolumny widoczne w DESCRIBE
- [ ] Formularz skrzynki: dwa edytory Quill z email-safe toolbar
- [ ] Zapis i odczyt header/footer dziala
- [ ] E-mail wysylany ze skrzynka z header/footer zawiera zlozony layout
- [ ] E-mail wysylany ze skrzynka BEZ header/footer zawiera tylko body
- [ ] Preview pokazuje zlozony wynik
- [ ] Zmienne w header/footer sa rozwiazywane
- [ ] Brak bledow PHP/JS w konsoli
- Wszystkie 3 taski zakonczone
- Wszystkie 4 acceptance criteria spelnione
- Wszystkie verification checks przeszly
- Brak regresji w istniejacym wysylaniu e-mail