# Sharing Templates With Marketing

Source: https://wraps.dev/docs/guides/template-handoff

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.

emails/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

| Change | Where | Review |
| --- | --- | --- |
| Layout, components, new variables | Repo, then `push` | Pull request |
| Subject line, headline, body copy | Dashboard | Preview and a test send |
| Anything sent to every customer | Either, then a test send | A 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.

[Templates as code](https://wraps.dev/docs/guides/templates)[Wraps for marketing teams](https://wraps.dev/for/marketing)
