# Mejl i kod: se dem, migrera dem, förbättra dem

Kanonisk URL: https://brew.new/sv/blog/emails-in-code-visibility-migration-mcp
Författare: Thomas Park
Publicerad: 2026-10-12
Uppdaterad: 2026-10-15

Mejl i kod är produktmejl som definieras i din kodbas och släpps som vilken annan funktion som helst. Fråga ett team som bygger på Resend och React Email vilka mejl produkten skickar, så blir det tyst en stund. Välkomstmejlet är en komponent i `emails/`. Lösenordsåterställningen finns i autentiseringsleverantörens kontrollpanel. Mejlet om att provperioden tar slut är en sträng i ett cron-jobb. Stripe skickar kvittot. Ingen minns vad som skickar mejlet ”vi saknar dig”.

Varje mejl var lätt att släppa, så ingen äger helheten. Vi skulle åtgärda det i den här ordningen: först överblick, sedan migrering och sist förbättring. Kodagenten som du redan använder kan göra det mesta, ansluten till Brew via MCP.

## Det viktigaste i korthet

- Börja med en inventering, inte med ett nytt verktyg: varje mejl, dess utlösare, dess fil och dess variabler.
- En kodagent kan hitta anropen som skickar mejl, rendera varje mall och importera den med MCP-verktyget `import_email`. Importen är deterministisk, använder ingen AI och är gratis.
- Importerade mejl hamnar på en canvas per grupp, så att teamet kan granska dem sida vid sida i dator- och mobilvy.
- Vid migreringen blir ett utskick i Resend-stil en utlösare plus en publicerad automatisering, som startas med ett enda REST-anrop. Produktionskod använder REST, inte MCP.
- Flytta en mejltyp i taget bakom en flagga, och flytta transaktionsmejlen sist.

## Därför blir mejl som definieras i kod osynliga

React Email och Resend gjorde e-post betydligt bättre för utvecklare: typade komponenter som granskas i pull requests. Kostnaden visar sig senare.

**Mejlen är utspridda.** Komponenter, inline-HTML i ett bakgrundsjobb, mallar i en kontrollpanel hos en tredje part, mejl som en annan tjänst skickar åt dig. Ingen fil listar dem alla.

**Förhandsvisningar är inte produktion.** Förhandsvisningsservern i React Email visar en mall med dess props för förhandsvisning, inte det som en kund faktiskt fick, och inget alls för andra mallar.

**Bara utvecklare kan titta.** Produkt, design och support skulle behöva be en utvecklare att köra projektet, så oftast gör de inte det.

**Ingenting granskas efter lanseringen.** Det två år gamla mejlet har fortfarande den gamla logotypen och en plan som du inte längre säljer. Ingen märker det, eftersom ingen ser mejlen tillsammans.

## Steg 1: Överblick

Målet är en gemensam vy över alla mejl. Inget av det här rör produktionen.

![De tre faserna när mejl flyttas ut ur koden: överblick med import_email och en canvas per grupp, migrering med domäner, kontakter, utlösare och automatiseringar, samt optimering med granskningar, renderingstester och statistik.](/images/blog/emails-in-code-three-phases.png)

*Varje fas är användbar i sig, och du kan stanna efter vilken som helst av dem.*

### Anslut din kodagent till Brew

MCP-servern för Brew finns på `https://brew.new/api/mcp` och använder OAuth: du loggar in med Brew och väljer vilket varumärke agenten får arbeta i. I Claude Code:

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

När du lägger till servern sparas den bara i den lokala konfigurationen för det här projektet. Lägg till `--scope user` för att använda den i alla projekt. Kör `claude mcp login brew`, eller öppna `/mcp` och autentisera dig mot Brew, innan du använder verktygen i Brew.

[Dokumentationen om att konfigurera klienter](https://docs.brew.new/api-reference/mcp/connect-your-client) har avsnitt för Cursor och Codex. Klienter utan OAuth kan använda en varumärkesspecifik API-nyckel. Låt sedan agenten anropa `get_brew_capabilities`, så att den läser den aktuella verktygslistan i stället för att gissa.

### Bygg inventeringen

Be agenten om en lista:

```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.
```

Behåll avsnittet ”Unknown”. Bekräftelsemejl från autentiseringen och kvitton från Stripe skickas inte av något anrop i din kod, men de hör hemma på listan. Committa `EMAILS.md`. Även om du stannar här har du en lista som du inte hade i går.

### Rendera varje mall med realistisk data

Det bästa underlaget för importen är den HTML som din kod redan producerar. [Funktionen render](https://react.email/docs/utilities/render) i React Email ger dig den strängen:

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

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

Äldre projekt importerar `render` från `@react-email/render`. Använd realistisk testdata, inte `"test"`, eftersom ditt team ska granska mejlen. Låt agenten skripta en rendering av varje mall till en fil.

Brew kan också importera en React Email-modul direkt med `format: "jsx"`. Modulen läses då statiskt, utan att köras. Props får sina standardvärden eller värdena i `PreviewProps`, komponenter som importeras från andra lokala filer utelämnas eller avvisas, och `await` och liknande nekas. För allt utöver en enda fristående fil använder du renderad HTML.

### Importera allt till ett och samma ställe

Importera sedan varje rendering med `import_email`, med ett `groupName` per produktområde:

```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.
```

Utvecklare frågar med rätta om importen skriver om deras mejl. Det gör den inte:

- Den är en deterministisk kompilator, inte en AI-modell, och kostar inga krediter.
- Den skapar en redigerbar design i Brew, medan utskicket behåller den ursprungliga käll-HTML:en för de importerade raderna.
- Den hostar offentliga bilder på nytt, och `assetReport` listar de bilder som inte gick att hämta.
- Den tar bort skript, iframes, formulär och händelsehanterare, och oskadliggör befintliga spårningspixlar för öppningar.
- Varje källa får vara upp till 5 MB. Det går inte att ange en URL: verktyget tar emot själva koden, och `baseUrl` används bara för att lösa upp relativa sökvägar till bilder.

### Granska mejlen tillsammans i teamet

Varje grupp får en canvas. Öppna `brew.new/emails/onboarding`, så ligger hela sekvensen sida vid sida, med ett reglage för datorvy och mobilvy och kommentarer på varje design. Länkar till en canvas fungerar för personer som är inloggade i din arbetsyta i Brew. Det finns ingen offentlig delningslänk, så bjud in dem som ska granska.

Det är här problemen dyker upp: två logotyper, en död länk i en knapp, tre olika sidfötter. Kör `audit_email` innan du åtgärdar något. Verktyget kontrollerar länkar, bilder, regelefterlevnad, inläst storlek, klientstöd, tillgänglighet och text, kostar 5 krediter per fullständig granskning och tar emot ett sparat `emailId` eller rå HTML, så att du kan granska mejl som du inte har importerat. Vår [checklista för kvalitetskontroll före utskick](/sv/blog/pre-send-email-qa-checklist) förklarar varje kontroll och gränsvärde.

Nu har du en inventering, alla mejl på en canvas och en lista med problem, och din kod skickar fortfarande allt precis som förut.

## Steg 2: Migrering

Nu skickar Brew en del mejl i stället för din kod. Alla mejl behöver inte flyttas, och bara ett system åt gången skickar en viss mejltyp.

### Koppla ihop begreppen

| I din kod i dag | I Brew |
| --- | --- |
| `resend.emails.send({ react: <WelcomeEmail /> })` | En utlösarhändelse plus en publicerad automatisering med ett steg av typen Skicka mejl |
| Props som skickas till komponenten | Fält i utlösarens nyttolast, som används som personaliseringstaggar, till exempel `{{ firstName \| there }}` eller `{{ trigger.planName }}` |
| Domänen i `from` | En verifierad avsändardomän i Brew |
| Din användartabell | Kontakter som skapas via REST-API:t eller en integration |
| Ett cron-jobb som skickar en sekvens | En automatisering med steg av typen Vänta, Filter och Skicka mejl |

### 1. Konfigurera avsändardomänen

Agenten anropar `create_domain` för att få DNS-posterna och sedan `verify_domain` när posterna är publicerade. Domäner som skapas via MCP och API är marknadsföringsdomäner. För lösenordsåterställningar och kvitton konfigurerar du en domän med transaktionellt syfte i webbappen: den hoppar över sidfoten för avregistrering och levererar även till avregistrerade kontakter. [Verifiera din avsändardomän](https://docs.brew.new/get-started/verify-your-sending-domain) går igenom posterna.

### 2. Ta in kontakter

MCP uppdaterar kontakter men skapar dem inte. Skapa dem från din backend med `POST /v1/contacts`, som skapar eller uppdaterar en kontakt eller en omgång på upp till 1 000 och skapar anpassade fält automatiskt, eller ladda upp en CSV-fil i appen. När du skickar en händelse till en utlösare skapas eller uppdateras också kontakten i nyttolasten. Importera bara personer som har sagt ja till att höra från dig. [Lägg till kontakter](https://docs.brew.new/audience/add-contacts) förklarar vad Brew förväntar sig.

### 3. Byt ut förhandsvisningsvärden mot personaliseringstaggar

Lätt att missa: det importerade välkomstmejlet säger bokstavligen ”Hi Ana”. Byt ut varje testvärde mot en personaliseringstagg som `{{ firstName | there }}`, där texten efter lodstrecket är reservvärdet. Använd editorn i Brew, eller låt agenten köra `edit_email` begränsat till just de ersättningarna. Det är en redigering med AI som förbrukar krediter, så kontrollera förhandsvisningen. [Personaliseringstaggar](https://docs.brew.new/create-emails/merge-tags) listar syntaxen.

### 4. Skapa utlösaren och automatiseringen

Agenten skapar en utlösare med `save_trigger`. Nyttolastens schema måste innehålla mottagarens `email`. Ge utlösaren ett typat kontrakt (`infer_payload_contract` tar fram ett utkast utifrån en exempelnyttolast, `set_trigger_contract` sparar det) och hämta det sedan med `get_trigger_contract` i formatet `ts` eller `zod`, så att din backend och Brew är överens om nyttolastens form.

Sedan bygger `save_automation` utlösaren plus ett steg av typen Skicka mejl med mejlet, domänen och ämnesraden. Automatiseringen förblir ett utkast tills du publicerar den. `dryRun` validerar flödet, `test_automation` skickar ett riktigt exemplar till din adress och `check_trigger` bekräftar att en publicerad automatisering lyssnar innan produktionen skickar några händelser.

### 5. Byt ut anropet

Det här är den enda ändringen i din applikationskod. Före, med 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} />,
})
```

Efter, med 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` kastar inget fel när servern avvisar anropet, så kontrollera `res.ok` och låt systemet som kör dina jobb göra nya försök eller larma. Slutpunkten returnerar `202` när den tar emot händelsen och `422` om ingen publicerad automatisering lyssnar på utlösaren.

Bygg `Idempotency-Key` utifrån ett stabilt id, till exempel användarens eller beställningens. En upprepad nyckel returnerar den ursprungliga körningen i stället för att skicka två gånger, och om du återanvänder en nyckel med ett annat innehåll i anropet får du `409 IDEMPOTENCY_CONFLICT`. Brew sparar nyckeln för en händelse lika länge som utlösarens kvitto, alltså i 90 dagar, inte de 24 timmar som många API:er använder. Behandla nycklar som permanenta, en per åtgärd. En nyckel som bygger på en tidsstämpel ändras vid varje nytt försök, och en nyckel som återanvänds för en annan åtgärd flera veckor senare kan behandlas som en upprepning i stället för att skickas.

Produktionskod anropar den här slutpunkten med en API-nyckel, inte via MCP. Via OAuth ber MCP en person att bekräfta skarpa utskick, händelser till utlösare och publiceringar: rätt i en chatt, fel i koden som hanterar ett inkommande anrop. [Guiden om MCP kontra REST](/sv/blog/mcp-vs-rest-api-for-email-marketing-which-workflow-should-you-use) går igenom uppdelningen.

### 6. Byt över ett mejl i taget

1. Lägg det nya anropet bakom en funktionsflagga bredvid det gamla, så att det räcker med en konfigurationsändring för att byta tillbaka.
2. Börja med ett mejl med låg risk, till exempel en påminnelse i livscykeln, och bevaka leveranser och klick i några dagar.
3. Flytta sekvenserna därefter, när deras utlösare får rätt nyttolaster.
4. Flytta transaktionsmejlen sist. Användarna märker trasiga lösenordsåterställningar och magiska länkar först.
5. Ta bort den gamla vägen efter en vecka utan problem, och uppdatera `EMAILS.md`.

[Migreringsguiden för Loops](https://docs.brew.new/migrations/loops) följer samma mönster.

### Det här bör stanna i koden

Bekräftelsemejl och mejl med magiska länkar från Supabase fortsätter att komma från Supabase. Supabase-integrationen i Brew lyssnar efter användarhändelser för att starta automatiseringar, i stället för att ersätta mejlen från Supabase Auth. En specificerad faktura som byggs av komplex data vid utskickstillfället kan också vara enklare att ha kvar i koden, så kontrollera först vad dina personaliseringstaggar behöver. Behåll importerade kopior på canvasen, så att även de här mejlen syns.

## Steg 3: Optimering

**Granska alla mejl.** Kör `audit_email` på en hel grupp, åtgärda och kör igen före stora utskick.

**Kontrollera riktiga e-postklienter.** `test_email_rendering` returnerar skärmbilder från specifika e-postklienter och enheter, även i mörkt läge, för 10 krediter per körning. `create_inbox_placement_test` skickar ett riktigt exemplar till testinkorgar och rapporterar var det hamnade, också för 10 krediter.

**Gör mejlen enhetliga.** Rätta inkonsekvenser i editorn, eller låt agenten göra en ändring, till exempel en ny sidfot, i en hel grupp med `edit_email`.

**Läs resultaten.** `get_email_analytics` rapporterar utskick och engagemang. `list_insights` returnerar fynd som Brew har upptäckt i ditt konto, vart och ett med måtten bakom.

**Testa varianter medvetet.** Skapa varianter på canvasen och styr kontakter med ett steg av typen Förgrening utifrån beteende, till exempel om de klickade. Brew utser ingen vinnare av en slumpmässig uppdelning åt dig, så jämför varianterna själv.

**Håll nya mejl synliga.** Lägg till en rad i `AGENTS.md` eller motsvarande: nya produktmejl skapas i Brew, startas via en utlösare och läggs till i `EMAILS.md` i samma pull request.

## Vanliga frågor

### Kan Brew importera React Email-mallar?

Ja. `import_email` tar emot renderad HTML, MJML, ett sparat `.eml`-meddelande eller en React Email-modul. Renderad HTML är mest tillförlitligt, eftersom en modul läses statiskt: props får sina standard- eller förhandsvisningsvärden, och komponenter som importeras från andra lokala filer utelämnas.

### Ansluter Brew till mitt Resend-konto?

Nej. Det finns ingen integration, import eller export för Resend. Du importerar den HTML som din kod renderar, och om du migrerar skickar din kod en händelse till en utlösare i Brew i stället för att anropa Resend.

### Ska min backend anropa Brew via MCP?

Nej. Använd MCP från en kodagent för konfiguration, importer, granskningar och tester. Produktionskod bör anropa REST-API:t med en API-nyckel, eftersom MCP vid anslutningar via OAuth ber en person att bekräfta skarpa utskick och händelser till utlösare.

### Ändrar importen hur mejlet ser ut?

Importen gör om mejlet till en redigerbar design i Brew och behåller den ursprungliga käll-HTML:en för utskicket. Den tar bort skript, iframes, formulär och händelsehanterare, oskadliggör befintliga spårningspixlar och hostar offentliga bilder på nytt. Granska varje mejl på canvasen, i datorvy och mobilvy, innan du skickar det från Brew.
