# E-maile w kodzie: zobacz je, przenieś i ulepsz

Kanoniczny adres URL: https://brew.new/pl/blog/emails-in-code-visibility-migration-mcp
Autor: Thomas Park
Opublikowano: 2026-10-12
Zaktualizowano: 2026-10-15

E-maile w kodzie to e-maile produktowe zdefiniowane w Twojej bazie kodu i wdrażane jak każda inna funkcja. Zapytaj zespół, który pracuje na Resend i React Email, jakie e-maile wysyła produkt, a zapadnie chwila ciszy. E-mail powitalny to komponent w `emails/`. Reset hasła siedzi w panelu dostawcy uwierzytelniania. E-mail o końcu okresu próbnego to ciąg znaków w zadaniu crona. Potwierdzenie płatności wysyła Stripe. A co wysyła e-mail „Tęsknimy za Tobą”, tego nikt już nie pamięta.

Każdy z tych e-maili łatwo było wdrożyć, więc nikt nie odpowiada za całość. My uporządkowalibyśmy to w takiej kolejności: najpierw widoczność, potem migracja, na końcu ulepszenia. Większość tej pracy wykona agent kodujący, którego już używasz, połączony z Brew przez MCP.

## Najważniejsze wnioski

- Zacznij od inwentaryzacji, a nie od nowego narzędzia: każdy e-mail, jego wyzwalacz, plik i zmienne.
- Agent kodujący może znaleźć wywołania wysyłki, wyrenderować każdy szablon i zaimportować go narzędziem MCP `import_email`. Import jest deterministyczny, nie korzysta z AI i jest bezpłatny.
- Zaimportowane e-maile trafiają na kanwy – po jednej na grupę – więc zespół przegląda je obok siebie w widoku na komputer i na telefon.
- Przy migracji wysyłka w stylu Resend zamienia się w wyzwalacz i opublikowaną automatyzację, którą uruchamia jedno wywołanie REST. Kod produkcyjny korzysta z REST, a nie z MCP.
- Przenoś po jednym typie e-maila naraz, za flagą funkcji, a e-maile transakcyjne przenieś na końcu.

## Dlaczego e-maile zdefiniowane w kodzie stają się niewidoczne

React Email i Resend bardzo ułatwiły programistom pracę z e-mailami: typowane komponenty, przeglądane w pull requestach. Koszt wychodzi na jaw później.

**E-maile są rozproszone.** Komponenty, HTML wpisany na sztywno w zadaniu działającym w tle, szablony w panelu zewnętrznej usługi, e-maile, które inna usługa wysyła za Ciebie. Żaden plik nie wymienia ich wszystkich.

**Podgląd to nie produkcja.** Serwer podglądu React Email pokazuje jeden szablon z jego propsami podglądu, a nie to, co faktycznie dostał klient – a dla pozostałych szablonów nie pokazuje niczego.

**Zajrzeć mogą tylko programiści.** Osoby z zespołów produktu, projektowania i wsparcia musiałyby prosić programistę o uruchomienie projektu, więc zwykle tego nie robią.

**Po wdrożeniu nikt niczego nie przegląda.** Dwuletni e-mail nadal ma stare logo i plan, którego już nie sprzedajesz. Nikt tego nie zauważa, bo nikt nie widzi tych e-maili razem.

## Krok 1: widoczność

Celem jest jeden wspólny widok wszystkich e-maili. Nic na tym etapie nie dotyka produkcji.

![Trzy etapy przenoszenia e-maili z kodu: widoczność dzięki import_email i kanwie dla każdej grupy, migracja z domenami, kontaktami, wyzwalaczami i automatyzacjami oraz optymalizacja z audytami, testami renderowania i analityką.](/images/blog/emails-in-code-three-phases.png)

*Każdy etap jest przydatny sam w sobie i po każdym z nich możesz się zatrzymać.*

### Połącz agenta kodującego z Brew

Serwer MCP Brew działa pod adresem `https://brew.new/api/mcp` i korzysta z OAuth: logujesz się w Brew i wybierasz markę, w której agent może pracować. W Claude Code:

```bash
claude mcp add --transport http brew "https://brew.new/api/mcp"
```

Dodanie serwera zapisuje go tylko w lokalnej konfiguracji tego projektu. Dodaj `--scope user`, aby był dostępny we wszystkich projektach. Zanim skorzystasz z jego narzędzi, uruchom `claude mcp login brew` albo otwórz `/mcp` i uwierzytelnij się w Brew.

[Dokumentacja konfiguracji klientów](https://docs.brew.new/api-reference/mcp/connect-your-client) zawiera też instrukcje dla Cursor i Codex. Aplikacje klienckie bez obsługi OAuth mogą korzystać z klucza API przypisanego do marki. Potem poproś agenta o wywołanie `get_brew_capabilities`, żeby odczytał aktualną listę narzędzi, zamiast zgadywać.

### Zrób inwentaryzację

Poproś agenta o listę:

```text
Find every email this codebase sends. Search for provider calls
(resend.emails.send, postmark, sendgrid, nodemailer, SES), React Email
components and render() calls, and any email templates referenced from
config or a third-party dashboard.

For each email, record: a name, what triggers it, the file and line that
sends it, the template file, the provider, and the variables it uses.
Write the result as a table in EMAILS.md. List anything you could not
resolve under "Unknown" instead of guessing.
```

Zachowaj sekcję „Unknown”. Potwierdzenia wysyłane przez system uwierzytelniania i potwierdzenia płatności ze Stripe nie są wywołaniami wysyłki w Twoim kodzie, ale też należą do listy. Dodaj plik `EMAILS.md` do repozytorium. Nawet jeśli na tym poprzestaniesz, masz listę, której wczoraj nie było.

### Wyrenderuj każdy szablon z realistycznymi danymi

Najlepszym materiałem do importu jest HTML, który Twój kod już generuje. Dostaniesz go jako ciąg znaków z [funkcji render](https://react.email/docs/utilities/render) w React Email:

```tsx
import { render } from 'react-email'
import WelcomeEmail from './emails/welcome'

const html = await render(
  <WelcomeEmail firstName="Ana" planName="Pro" trialDays={14} />
)
```

Starsze projekty importują `render` z `@react-email/render`. Używaj realistycznych danych testowych, a nie `"test"`, bo te e-maile będzie przeglądał Twój zespół. Poproś agenta o skrypt, który wyrenderuje każdy szablon do pliku.

Brew potrafi też zaimportować moduł React Email bezpośrednio, z `format: "jsx"`: odczytuje go statycznie, bez uruchamiania. Propsy przyjmują wartości domyślne lub te z `PreviewProps`, komponenty importowane z innych lokalnych plików są pomijane lub odrzucane, a `await` i podobne konstrukcje nie są akceptowane. Jeśli szablon nie mieści się w jednym samodzielnym pliku, użyj wyrenderowanego HTML.

### Zaimportuj wszystko w jedno miejsce

Potem zaimportuj każdy wyrenderowany plik za pomocą `import_email`, z jednym `groupName` na obszar produktu:

```text
For each rendered email in ./email-renders, call import_email with
format "html", the title and subject line from EMAILS.md, and a
groupName by area: "Auth", "Onboarding", "Billing" or "Lifecycle".
Report the emailId for each one and any warnings in its assetReport.
```

Programiści słusznie pytają, czy import przepisuje ich e-maile. Nie przepisuje:

- To deterministyczny kompilator, a nie model AI, i nie zużywa kredytów.
- Tworzy edytowalny projekt w Brew, a przy wysyłce zaimportowane wiersze zachowują oryginalny źródłowy HTML.
- Przenosi publiczne obrazy na hosting Brew, a `assetReport` wymienia te, których nie udało się pobrać.
- Usuwa skrypty, elementy iframe, formularze i obsługę zdarzeń (event handlers) oraz neutralizuje istniejące piksele śledzące otwarcia.
- Każde źródło może mieć do 5 MB. Nie da się podać adresu URL: narzędzie przyjmuje sam kod, a `baseUrl` służy tylko do rozwiązywania względnych ścieżek obrazów.

### Przejrzyj e-maile razem z zespołem

Każda grupa dostaje własną kanwę. Otwórz `brew.new/emails/onboarding`, a zobaczysz obok siebie całą sekwencję, z przełącznikiem widoku na komputer i telefon oraz komentarzami przy każdym projekcie. Linki do kanwy działają dla osób zalogowanych do Twojego obszaru roboczego Brew. Nie ma publicznego linku do udostępniania, więc zaproś osoby, które mają przeglądać e-maile.

Tu wychodzą na jaw problemy: dwa logotypy, martwy link w przycisku, trzy różne stopki. Zanim cokolwiek poprawisz, uruchom `audit_email`. Narzędzie sprawdza linki, obrazy, zgodność z przepisami, łączny rozmiar do pobrania, obsługę w programach pocztowych, dostępność i treść. Pełny audyt kosztuje 5 kredytów, a narzędzie przyjmuje zapisany `emailId` albo surowy HTML, więc możesz audytować także e-maile, których nie zaimportowano. Nasza [lista kontrolna przed wysyłką](/pl/blog/pre-send-email-qa-checklist) wyjaśnia każdy test i każdy próg.

Masz teraz inwentarz, wszystkie e-maile na kanwie i listę problemów, a Twój kod nadal wysyła wszystko tak jak wcześniej.

## Krok 2: migracja

Teraz część e-maili zamiast Twojego kodu wysyła Brew. Nie każdy e-mail musi zostać przeniesiony, a dany typ e-maila w każdej chwili wysyła tylko jeden system.

### Przełóż pojęcia

| Dziś w Twoim kodzie | W Brew |
| --- | --- |
| `resend.emails.send({ react: <WelcomeEmail /> })` | Zdarzenie wyzwalające i opublikowana automatyzacja z jednym krokiem „Wyślij e-mail” |
| Propsy przekazywane do komponentu | Pola danych zdarzenia wyzwalacza, używane jako tagi personalizacji, np. `{{ firstName \| there }}` lub `{{ trigger.planName }}` |
| Domena w polu `from` | Zweryfikowana domena wysyłkowa w Brew |
| Twoja tabela użytkowników | Kontakty tworzone przez REST API lub integrację |
| Zadanie cron, które wysyła sekwencję | Automatyzacja z krokami „Czekaj”, „Filtr” i „Wyślij e-mail” |

### 1. Skonfiguruj domenę wysyłkową

Agent wywołuje `create_domain`, żeby uzyskać rekordy DNS, a gdy rekordy będą już aktywne – `verify_domain`. Domeny dodane przez MCP i API to domeny marketingowe. Do resetów haseł i potwierdzeń płatności skonfiguruj w aplikacji Brew domenę o przeznaczeniu transakcyjnym: pomija ona stopkę z linkiem do wypisania i dostarcza e-maile także do wypisanych kontaktów. Rekordy opisuje strona [Weryfikacja domeny wysyłkowej](https://docs.brew.new/get-started/verify-your-sending-domain).

### 2. Przenieś kontakty

MCP aktualizuje kontakty, ale ich nie tworzy. Twórz je z backendu przez `POST /v1/contacts` (endpoint tworzy lub aktualizuje jeden kontakt lub partię do 1000 kontaktów i automatycznie zakłada pola własne) albo prześlij plik CSV w aplikacji. Wywołanie wyzwalacza również tworzy lub aktualizuje kontakt na podstawie danych zdarzenia. Importuj tylko osoby, które zgodziły się otrzymywać od Ciebie wiadomości. Strona [Dodawanie kontaktów](https://docs.brew.new/audience/add-contacts) wyjaśnia, czego oczekuje Brew.

### 3. Zastąp wartości podglądu tagami personalizacji

Łatwo to przeoczyć: w zaimportowanym e-mailu powitalnym dosłownie widnieje „Hi Ana”. Zastąp każdą wartość testową tagiem personalizacji, np. `{{ firstName | there }}`, gdzie tekst po pionowej kresce to wartość zastępcza. Zrób to w edytorze Brew albo poproś agenta o uruchomienie `edit_email` ograniczonego do tych zamian. To edycja przez AI, która zużywa kredyty, więc sprawdź podgląd. Składnię opisuje strona [Tagi personalizacji](https://docs.brew.new/create-emails/merge-tags).

### 4. Utwórz wyzwalacz i automatyzację

Agent tworzy wyzwalacz za pomocą `save_trigger`. Schemat danych zdarzenia musi zawierać pole `email` odbiorcy. Zdefiniuj typowany schemat (`infer_payload_contract` przygotowuje jego szkic na podstawie przykładowych danych zdarzenia, a `set_trigger_contract` go zapisuje), a potem pobierz go przez `get_trigger_contract` w formacie `ts` lub `zod`, żeby Twój backend i Brew zgadzały się co do kształtu danych zdarzenia.

Następnie `save_automation` buduje wyzwalacz i krok „Wyślij e-mail” z e-mailem, domeną i tematem. Automatyzacja pozostaje wersją roboczą, dopóki jej nie opublikujesz. `dryRun` sprawdza poprawność schematu automatyzacji, `test_automation` wysyła prawdziwą kopię na Twój adres, a `check_trigger` potwierdza, że opublikowana automatyzacja nasłuchuje, zanim produkcja cokolwiek wywoła.

### 5. Podmień miejsce wywołania

To jedyna zmiana w kodzie Twojej aplikacji. Wcześniej, z Resend:

```ts
await resend.emails.send({
  from: 'Acme <hello@acme.com>',
  to: user.email,
  subject: 'Welcome to Acme',
  react: <WelcomeEmail firstName={user.firstName} planName={plan.name} />,
})
```

Potem, z Brew:

```ts
const res = await fetch(
  `https://brew.new/api/v1/automations/triggers/${process.env.BREW_WELCOME_TRIGGER_ID}/fire`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.BREW_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `welcome-${user.id}`,
    },
    body: JSON.stringify({
      payload: { email: user.email, firstName: user.firstName, planName: plan.name },
    }),
  }
)

if (!res.ok) {
  // 422 means the automation is still a draft; 400 means the payload failed the schema.
  throw new Error(`Brew trigger failed: ${res.status} ${await res.text()}`)
}
```

`fetch` kończy się powodzeniem nawet wtedy, gdy żądanie zostanie odrzucone, więc sprawdzaj `res.ok` i pozwól, by system obsługi zadań ponowił próbę albo wysłał alert. Endpoint zwraca `202`, gdy przyjmie zdarzenie, i `422`, jeśli żadna opublikowana automatyzacja nie nasłuchuje tego wyzwalacza.

Twórz `Idempotency-Key` ze stałego identyfikatora, np. użytkownika lub zamówienia. Powtórzony klucz zwraca pierwotne uruchomienie zamiast drugiej wysyłki, a użycie tego samego klucza z inną treścią żądania kończy się błędem `409 IDEMPOTENCY_CONFLICT`. Brew przechowuje klucz wywołania tak długo jak rekord odebrania wyzwalacza, czyli 90 dni, a nie 24 godziny, jak wiele innych API. Traktuj klucze jako trwałe – jeden na działanie. Klucz oparty na znaczniku czasu zmienia się przy każdym ponowieniu, a klucz użyty kilka tygodni później do innego działania może zwrócić wcześniejsze uruchomienie, zamiast wysłać e-mail.

Kod produkcyjny wywołuje ten endpoint z kluczem API, a nie przez MCP. W połączeniu OAuth MCP prosi człowieka o potwierdzenie rzeczywistych wysyłek, wywołań wyzwalaczy i publikacji: to dobre rozwiązanie na czacie, ale złe w kodzie obsługującym żądania. Podział pracy opisuje [poradnik MCP kontra REST](/pl/blog/mcp-vs-rest-api-for-email-marketing-which-workflow-should-you-use).

### 6. Przełączaj po jednym e-mailu

1. Umieść nowe wywołanie za flagą funkcji obok starego, żeby powrót wymagał tylko zmiany konfiguracji.
2. Zacznij od e-maila o niskim ryzyku, np. przypomnienia w cyklu życia, i przez kilka dni obserwuj dostarczenia i kliknięcia.
3. Następnie przenieś sekwencje, gdy ich wyzwalacze będą wywoływane z prawidłowymi danymi zdarzeń.
4. E-maile transakcyjne przenieś na końcu. Niedziałające resety haseł i magiczne linki użytkownicy zauważają najszybciej.
5. Po tygodniu bez problemów usuń starą ścieżkę i zaktualizuj `EMAILS.md`.

[Przewodnik po migracji z Loops](https://docs.brew.new/migrations/loops) stosuje ten sam schemat.

### Co powinno zostać w kodzie

E-maile potwierdzające i e-maile z magicznym linkiem nadal wysyła Supabase. Integracja Brew z Supabase nasłuchuje zdarzeń użytkowników, żeby uruchamiać automatyzacje, a nie zastępuje e-maili Supabase Auth. Szczegółowa faktura budowana w chwili wysyłki ze złożonych danych też może być prostsza w kodzie, więc najpierw sprawdź, czego potrzebują Twoje tagi personalizacji. Zostaw zaimportowane kopie na kanwie, żeby te e-maile pozostały widoczne.

## Krok 3: optymalizacja

**Audytuj cały zestaw.** Uruchom `audit_email` dla całej grupy, popraw błędy i powtórz audyt przed dużymi wysyłkami.

**Sprawdź w prawdziwych programach pocztowych.** `test_email_rendering` zwraca zrzuty ekranu z konkretnych programów pocztowych i urządzeń, także w trybie ciemnym, za 10 kredytów za uruchomienie. `create_inbox_placement_test` wysyła prawdziwą kopię do skrzynek testowych i raportuje, gdzie trafiła – również za 10 kredytów.

**Zadbaj o spójność zestawu.** Popraw niespójności w edytorze albo poproś agenta, żeby za pomocą `edit_email` wprowadził jedną zmianę, np. nową stopkę, w całej grupie.

**Analizuj wyniki.** `get_email_analytics` raportuje wysyłki i zaangażowanie. `list_insights` zwraca spostrzeżenia, które Brew wykrył na Twoim koncie, każde z danymi, na których się opiera.

**Testuj warianty świadomie.** Utwórz warianty na kanwie i kieruj kontakty krokiem „Podział” na podstawie zachowania, np. tego, czy ktoś kliknął. Brew nie wybiera za Ciebie zwycięzcy losowego podziału, więc porównaj warianty samodzielnie.

**Dbaj o widoczność nowych e-maili.** Dodaj do `AGENTS.md` lub podobnego pliku regułę: nowe e-maile produktowe powstają w Brew, są uruchamiane wyzwalaczem i trafiają do `EMAILS.md` w tym samym pull requeście.

## Najczęściej zadawane pytania

### Czy Brew może zaimportować szablony React Email?

Tak. `import_email` przyjmuje wyrenderowany HTML, MJML, zapisaną wiadomość `.eml` albo moduł React Email. Najbardziej niezawodny jest wyrenderowany HTML, bo moduł jest odczytywany statycznie: propsy przyjmują wartości domyślne lub wartości podglądu, a komponenty importowane z innych lokalnych plików są pomijane.

### Czy Brew łączy się z moim kontem Resend?

Nie. Brew nie ma integracji z Resend ani funkcji importu czy eksportu dla Resend. Importujesz HTML, który renderuje Twój kod, a jeśli przeprowadzisz migrację, Twój kod wywołuje wyzwalacz Brew zamiast Resend.

### Czy mój backend powinien wywoływać Brew przez MCP?

Nie. Używaj MCP z poziomu agenta kodującego do konfiguracji, importów, audytów i testów. Kod produkcyjny powinien wywoływać REST API z kluczem API, bo w połączeniach OAuth MCP prosi człowieka o potwierdzenie rzeczywistych wysyłek i wywołań wyzwalaczy.

### Czy import zmienia wygląd e-maila?

Import zamienia e-mail w edytowalny projekt Brew i zachowuje oryginalny źródłowy HTML do wysyłki. Usuwa skrypty, elementy iframe, formularze i obsługę zdarzeń, neutralizuje istniejące piksele śledzące i przenosi publiczne obrazy na hosting Brew. Zanim wyślesz e-mail z Brew, przejrzyj każdy z nich na kanwie, w widoku na komputer i na telefon.
