Wraps Logo
DocsHome
Guide

Sharing templates with marketing

Engineers keep templates in git. Marketing wants to change the subject line without opening a pull request. Both are reasonable, and the place they meet is a field called lastEditedFrom.

Separate structure from copy

Do this first and most of the process problem disappears. A template where every editable string is a prop with a sensible default gives marketing real room to work without letting a copy change break the layout or drop a variable the send depends on.

Reactemails/welcome.tsx
// The split that makes this work: structure is code, copy is data.//// Engineers own the layout, the components, and the variable contract.// Marketing owns the words that go in the slots.import { Button, Heading, Section, Text } from "@react-email/components";type Props = {  firstName: string;  // Every string marketing is allowed to change is a prop with a default.  // The default is what ships if nobody touches it.  headline?: string;  body?: string;  ctaLabel?: string;};export default function Welcome({  firstName,  headline = "Welcome aboard",  body = "Your account is ready. Here is how to get started.",  ctaLabel = "Open the dashboard",}: Props) {  return (    <Section>      <Heading>{headline}</Heading>      <Text>Hi {firstName},</Text>      <Text>{body}</Text>      <Button href="https://app.example.com">{ctaLabel}</Button>    </Section>  );}

Who edits where

ChangeWhereReview
Layout, components, new variablesRepo, then pushPull request
Subject line, headline, body copyDashboardPreview and a test send
Anything sent to every customerEither, then a test sendA second person looks at it

How the two sides notice each other

Every template records where it was last edited, as either cli or dashboard. The CLI also keeps a hash of the source it last pushed. Between them, a push knows three things: whether your local file changed, whether the template still exists remotely, and whether somebody edited it in the dashboard since you last pushed.

terminal
# See what would change before anything moveswraps email templates push --dry-run# Push everything that changed since the last pushwraps email templates push# Push one templatewraps email templates push --template welcome# Overwrite a template that was edited on the dashboard.# This discards the dashboard version. There is no undo.wraps email templates push --template welcome --force

When marketing has edited a template and you push over it, the push does not fail. It syncs everything else and refuses that one:

terminal
$ wraps email templates push  ✔ Synced 4 templates to dashboard  ✖ welcome was edited on the dashboard. Use --force to overwrite.# The push did not fail. Four templates went out and one was refused,# because somebody changed it in the dashboard after your last push and# the CLI will not silently throw that away.## Note the exit code: it is 0. A refused template does not fail the# command, so a CI job that only checks the exit status goes green and# nobody reads the line above. If you push from CI, grep for it:  wraps email templates push | tee push.log  ! grep -q "Use --force to overwrite" push.log

Workflows behave the same way. A workflow last edited in the dashboard refuses a CLI push unless you pass --force.

The limit worth planning around

There is no templates pull

The CLI pushes. It does not pull a dashboard edit back down into your repo. So when marketing changes copy in the dashboard, that change lives only in the dashboard, and your next --force push overwrites it silently.

The conflict message is doing real work here: it is the signal to go copy the dashboard version into the template before you force. Treat a 409 as a task, not an obstacle.

A workflow that holds up

  • Engineers push from CI on merge to main, never from a laptop. The repo is then the only thing that can introduce a structural change.
  • Marketing edits copy in the dashboard and sends themselves a test before anything goes out.
  • When CI reports a conflict, whoever owns the template copies the dashboard wording into the repo, opens a small pull request, and pushes with --force after it merges. The repo becomes true again. Make CI actually report it — the push exits 0 on a conflict, so the job has to check the output rather than the status.
  • Run --dry-run in CI on pull requests so a reviewer can see which templates a merge would touch.

Next steps

The templates guide covers authoring and the push mechanics in full.