app
Resend Studio
Public Made by Adomby adom
A local editor for Resend email templates: paste your API key, then edit, preview, lint, test-send, and publish templates without Resend's dashboard. Also imports SendGrid dynamic templates and converts their Handlebars to Resend's variable syntax.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resend Studio</title>
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<div class="brand" data-tooltip-target="app-info" tabindex="0">
<img class="logo" src="favicon.svg" alt="" aria-hidden="true">
<span class="app-name">adom-resend-studio</span>
<span class="sep">/</span>
<span class="app-subtitle">Resend template editor</span>
</div>
<div class="vsep" aria-hidden="true"></div>
<div class="subject">
<span class="subject-name" id="subjectName">No template selected</span>
<span class="subject-meta" id="subjectMeta"></span>
</div>
<div class="actions">
<span id="sendgridStatus" class="status-pill" data-tooltip="Whether SendGrid import is available (add a SendGrid key in Settings). Import is read-only: it pulls each SendGrid template's ACTIVE version and never writes to SendGrid.">sendgrid</span>
<button id="resendStatus" class="status-pill as-btn" data-tooltip="Your Resend connection. Click to open Settings.">checking</button>
<button id="settingsBtn" class="icon-btn" data-tooltip="Settings: your Resend API key, an optional SendGrid key for importing, and sender defaults. Everything stays on this machine.">⚙</button>
</div>
<div class="hover-popover" id="app-info" role="dialog" aria-label="App information">
<div class="popover-title">adom-resend-studio</div>
<div class="popover-row"><span class="k">about</span><span class="v">Edit, preview, test, and publish Resend email templates. Edits stay local until you upload; uploads only change Resend's draft; real email changes only when you publish.</span></div>
<div class="popover-row"><span class="k">version</span><span class="v" id="popVersion"></span></div>
<div class="popover-row"><span class="k">shortcut</span><span class="v">Ctrl+S saves the local draft</span></div>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<span>templates</span>
<div class="sidebar-actions">
<button id="newBtn" class="icon-btn" data-tooltip="Start a new template from a blank starter. It is created as a local draft only; nothing reaches Resend until you upload it.">+</button>
<button id="importBtn" class="icon-btn wide" data-tooltip="Import templates from SendGrid. Converts Handlebars to Resend's {{{VAR}}} syntax and lets you decide what happens to each {{#if}} block, since Resend has no conditionals.">import</button>
<button id="refreshBtn" class="icon-btn" data-tooltip="Reload the template list from Resend. Local drafts are never touched by a refresh.">↻</button>
</div>
</div>
<div class="list-filter">
<input type="search" id="filterInput" placeholder="Filter templates" data-tooltip="Filter by name or alias.">
</div>
<div id="remoteError" class="remote-error" hidden></div>
<div id="templateList" class="template-list">
<div class="empty-state">Loading...</div>
</div>
<div class="legend">
<span><i class="dot live"></i>published</span>
<span><i class="dot draft"></i>on Resend, unpublished</span>
<span><i class="dot local"></i>local only</span>
</div>
</aside>
<main class="editor" id="editorArea">
<div class="empty-state large">
<div class="big-title">Pick a template on the left</div>
<div>or <button class="link-btn" id="emptyImportBtn">import from SendGrid</button> to start the migration.</div>
</div>
</main>
</div>
<template id="editorTemplate">
<div class="status-line" id="statusLine"></div>
<div class="meta-grid">
<label class="field"><span>Name</span><input type="text" id="nameInput" data-tooltip="Display name in Resend's dashboard. Not shown to recipients."></label>
<label class="field"><span>Alias</span><input type="text" id="aliasInput" class="mono" data-tooltip="The stable handle your code sends by, e.g. welcome-email. Changing it on a template that is already live breaks every sender still using the old alias."></label>
<label class="field wide"><span>Subject</span><input type="text" id="subjectInput" data-tooltip="Default subject. Variables work here too, as {{{KEY}}}."></label>
<label class="field"><span>From</span><input type="text" id="fromInput" data-tooltip="Default sender, e.g. Acme <[email protected]>, on a domain verified in Resend. A sender can override it per email."></label>
<label class="field"><span>Reply-to</span><input type="text" id="replyToInput" data-tooltip="Default reply-to address."></label>
<div class="toolbar-actions">
<button id="saveBtn" class="btn" data-tooltip="Save to this app's local database (Ctrl+S). Does not touch Resend.">Save</button>
<button id="uploadBtn" class="btn btn-accent" data-tooltip="Save, then send this draft to Resend. Creates the template if it doesn't exist there yet; otherwise updates Resend's DRAFT. Live email keeps using the published version until you publish.">Upload to Resend</button>
<button id="publishBtn" class="btn btn-danger" data-tooltip="Make Resend's current draft the version real emails use. Asks you to type the alias to confirm.">Publish</button>
<button id="moreBtn" class="icon-btn" data-tooltip="More actions">⋯</button>
<div class="menu" id="moreMenu" hidden>
<button data-act="load-remote">Replace editor with Resend's published copy</button>
<button data-act="discard">Discard local draft</button>
<button data-act="copy-json">Copy send snippet</button>
</div>
</div>
</div>
<div class="editor-panes">
<div class="pane">
<div class="pane-header">HTML <span class="pane-sub">variables are {{{KEY}}}; no conditionals or loops</span></div>
<textarea id="htmlInput" class="code-area" spellcheck="false"></textarea>
</div>
<div class="pane">
<div class="pane-header">
Preview <span class="pane-sub">substituted the way Resend does it</span>
<div class="seg" id="widthSeg">
<button data-w="desktop" class="on" data-tooltip="Full pane width.">desktop</button>
<button data-w="mobile" data-tooltip="375 px, a typical phone.">mobile</button>
</div>
</div>
<div class="inbox-head">
<div class="ih-row"><span class="k">from</span><span id="pvFrom"></span></div>
<div class="ih-row"><span class="k">subject</span><span id="pvSubject" class="pv-subject"></span></div>
</div>
<div class="preview-wrap" id="previewWrap">
<iframe id="previewFrame" class="preview-frame" title="Template preview" sandbox="allow-same-origin"></iframe>
</div>
</div>
</div>
<div class="lower-panes">
<div class="pane">
<div class="pane-header">
Variables <span class="pane-sub" id="varCount"></span>
<button id="syncVarsBtn" class="icon-btn wide small" data-tooltip="Add a row for every {{{KEY}}} used in the subject or HTML that isn't declared yet.">sync from HTML</button>
<button id="addVarBtn" class="icon-btn small" data-tooltip="Add a variable row.">+</button>
</div>
<div class="var-head">
<span>key</span><span>type</span>
<span data-tooltip="Used when a sender leaves the variable out. No fallback means the variable is required: Resend rejects a send without it.">fallback</span>
<span data-tooltip="Local only: the value used for the preview and draft test sends. Never uploaded.">sample</span><span></span>
</div>
<div id="variablesTable" class="variables-table"></div>
</div>
<div class="pane side-stack">
<div class="pane-header">Checks <span class="pane-sub" id="lintCount"></span></div>
<div id="lintList" class="lint-list"></div>
<div class="pane-header">Send a test</div>
<div class="send-test">
<input type="email" id="testEmailInput" placeholder="[email protected]" data-tooltip="Where the test goes. Only used for this send.">
<select id="testModeSelect" data-tooltip="'This draft' renders the editor's content here with your sample values and sends it as plain HTML, so you can test before uploading or publishing. 'Published version' sends through Resend's template engine, which only works once the template is published.">
<option value="draft">this draft</option>
<option value="published">published version</option>
</select>
<button id="sendTestBtn" class="btn" data-tooltip="Send one real email. The subject gets a [TEST] prefix in draft mode.">Send test</button>
</div>
<div class="pane-header">Notes <span class="pane-sub">local, for whoever picks this up next</span></div>
<textarea id="notesInput" class="notes-area" placeholder="Migration decisions, who sends this, what to watch for"></textarea>
<div class="pane-header">On Resend</div>
<div id="remoteInfo" class="remote-info"></div>
</div>
</div>
</template>
<div class="modal-backdrop" id="modalBackdrop" hidden>
<div class="modal" id="modal" role="dialog" aria-modal="true">
<div class="modal-head"><span id="modalTitle"></span><button class="icon-btn" id="modalClose" aria-label="Close">×</button></div>
<div class="modal-body" id="modalBody"></div>
<div class="modal-foot" id="modalFoot"></div>
</div>
</div>
<div id="toasts" class="toasts" aria-live="polite"></div>
<script src="app.js"></script>
</body>
</html>