From 59bbca32af3f761ebb3f7ab8c45d88f1a1fdc409 Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Tue, 25 Aug 2026 22:02:48 +0100 Subject: [PATCH 1/6] Replace MailerSend template sprawl with a repo-owned render endpoint Airtable's onboarding automation sent every welcome email via one of 7 hand-authored MailerSend templates, hardcoding chapter links/leaders that already drift from the live National Groups data, and only distinguishing volunteer vs non-volunteer recipients. Adds a render endpoint that composes subject/html/text from live chapter data, the recipient's actual intent (act-now/keep-informed/volunteer/lead), and language - so new chapters and languages no longer need a new MailerSend template + script branch. - src/lib/server/onboardingEmail/ - chapter lookup (reuses the National Groups fetch), intent/language copy, HTML + plain-text renderers - src/routes/api/onboarding-email - bearer-secret POST endpoint returning {subject, html, text}; render-only, never sends mail itself - src/routes/api/unsubscribe - suppresses on both MailerSend domains and flips the Members "Email subscription" checkbox; no unsubscribe mechanism existed in any of the 7 templates before this - src/routes/onboarding-email-preview - dev-only QA page, 404s outside dev - airtable-mailersend-emails.js - tries the new endpoint first, falls through to the untouched template_id logic on any failure - email-templates/ - raw MailerSend payloads for all 7 templates, kept as migration reference and to document the old routing gaps (Canada-FR was unreachable in practice; Lead hands off via mailto instead of a form submission) Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GtQwhn5Fb9eiC4LsmRs47q --- airtable-mailersend-emails.js | 271 ++++++++++++++++++ email-templates/ROUTING.md | 24 ++ .../canada-en-x2p0347j3r94zdrn.json | 85 ++++++ .../canada-fr-z86org8zmvkgew13.json | 85 ++++++ email-templates/default-3z0vkloo5v1l7qrx.json | 85 ++++++ .../not-volunteering-z86org8mdrelew13.json | 88 ++++++ email-templates/spanish-o65qngkj1mjlwr12.json | 85 ++++++ email-templates/uk-jy7zpl97rdrg5vx6.json | 85 ++++++ .../uk-non-volunteering-zr6ke4nyyomgon12.json | 85 ++++++ src/lib/server/onboardingEmail/blocks.ts | 95 ++++++ src/lib/server/onboardingEmail/chapter.ts | 99 +++++++ src/lib/server/onboardingEmail/copy.ts | 206 +++++++++++++ src/lib/server/onboardingEmail/html.ts | 95 ++++++ src/lib/server/onboardingEmail/index.ts | 44 +++ src/lib/server/onboardingEmail/language.ts | 68 +++++ src/lib/server/onboardingEmail/markdown.ts | 27 ++ src/lib/server/onboardingEmail/text.ts | 34 +++ src/lib/server/onboardingEmail/types.ts | 50 ++++ src/routes/api/onboarding-email/+server.ts | 69 +++++ src/routes/api/unsubscribe/+server.ts | 66 +++++ .../onboarding-email-preview/+page.server.ts | 92 ++++++ .../onboarding-email-preview/+page.svelte | 63 ++++ 22 files changed, 1901 insertions(+) create mode 100644 airtable-mailersend-emails.js create mode 100644 email-templates/ROUTING.md create mode 100644 email-templates/canada-en-x2p0347j3r94zdrn.json create mode 100644 email-templates/canada-fr-z86org8zmvkgew13.json create mode 100644 email-templates/default-3z0vkloo5v1l7qrx.json create mode 100644 email-templates/not-volunteering-z86org8mdrelew13.json create mode 100644 email-templates/spanish-o65qngkj1mjlwr12.json create mode 100644 email-templates/uk-jy7zpl97rdrg5vx6.json create mode 100644 email-templates/uk-non-volunteering-zr6ke4nyyomgon12.json create mode 100644 src/lib/server/onboardingEmail/blocks.ts create mode 100644 src/lib/server/onboardingEmail/chapter.ts create mode 100644 src/lib/server/onboardingEmail/copy.ts create mode 100644 src/lib/server/onboardingEmail/html.ts create mode 100644 src/lib/server/onboardingEmail/index.ts create mode 100644 src/lib/server/onboardingEmail/language.ts create mode 100644 src/lib/server/onboardingEmail/markdown.ts create mode 100644 src/lib/server/onboardingEmail/text.ts create mode 100644 src/lib/server/onboardingEmail/types.ts create mode 100644 src/routes/api/onboarding-email/+server.ts create mode 100644 src/routes/api/unsubscribe/+server.ts create mode 100644 src/routes/onboarding-email-preview/+page.server.ts create mode 100644 src/routes/onboarding-email-preview/+page.svelte diff --git a/airtable-mailersend-emails.js b/airtable-mailersend-emails.js new file mode 100644 index 000000000..8a0b17883 --- /dev/null +++ b/airtable-mailersend-emails.js @@ -0,0 +1,271 @@ +// User Inputs (set these in Airtable's script configuration) +let { + to_email, + first_name, + reply_to_email, + languages, + country, + onboarding_email, + airtable_id, + intent, + // Optional: override which render endpoint to call. Leave unmapped to use the + // production default below. + render_endpoint_url +} = input.config() +const apiKey = input.secret('MailerSend API key') +// New: shared secret for src/routes/api/onboarding-email/+server.ts (repo). Sent as +// "Authorization: Bearer ". If this secret isn't configured yet, the render +// attempt below fails closed (401) and falls through to the template_id path. +const renderSecret = input.secret('Onboarding email render secret') + +// A triggering record always has an ID, so this is empty only when the input variable is +// unmapped. Without the guard every email carries verificationKey=undefined and verification +// fails for the recipient with nothing failing here. +if (!airtable_id) { + throw new Error('airtable_id is missing - refusing to send a broken verification link') +} + +let verification_link = `https://pauseai.info/verify?table=join&verificationKey=${airtable_id}` + +// --------------------------------------------------------------------------------- +// New first attempt: ask the repo-owned render endpoint for {subject, html, text} +// and send that directly, instead of a MailerSend template_id. Render-only endpoint +// (never sends mail itself, never touches the MailerSend API key) - see +// src/lib/server/onboardingEmail/ and src/routes/api/onboarding-email/+server.ts. +// Any failure (network error, timeout, non-OK response, or a response missing any of +// subject/html/text) falls through to the existing template_id logic below, +// unchanged. This keeps the blast radius of the new endpoint at zero: worst case is +// today's behavior. +// --------------------------------------------------------------------------------- + +const RENDER_ENDPOINT_URL = render_endpoint_url || 'https://pauseai.info/api/onboarding-email' +const RENDER_TIMEOUT_MS = 8000 + +function withTimeout(promise, ms) { + return Promise.race([ + promise, + new Promise((_resolve, reject) => + setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms) + ) + ]) +} + +async function tryRenderEndpoint() { + let response + try { + response = await withTimeout( + fetch(RENDER_ENDPOINT_URL, { + method: 'POST', + headers: { + Authorization: `Bearer ${renderSecret}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ first_name, country, intent, languages, airtable_id }) + }), + RENDER_TIMEOUT_MS + ) + } catch (error) { + console.log( + `Render endpoint request failed (${error.message}) - falling back to template_id send` + ) + return null + } + + if (!response.ok) { + console.log(`Render endpoint returned ${response.status} - falling back to template_id send`) + return null + } + + let data + try { + data = await response.json() + } catch (error) { + console.log( + `Render endpoint response wasn't valid JSON (${error.message}) - falling back to template_id send` + ) + return null + } + + if (!data || !data.subject || !data.html || !data.text) { + console.log( + 'Render endpoint response missing subject/html/text - falling back to template_id send' + ) + return null + } + + return data +} + +const rendered = await tryRenderEndpoint() + +if (rendered) { + // Sender is a fixed rule, not chapter data: global default info@pauseai.info; UK + // standardizes on hello@pauseai.uk. Never pull an arbitrary "from" address out of + // Airtable - the sending domain must be verified in MailerSend. + const isUK = Boolean(country && country.trim().includes('United Kingdom')) + const from = isUK + ? { email: 'hello@pauseai.uk', name: 'PauseAI UK' } + : { email: 'info@pauseai.info', name: 'PauseAI' } + + // CC/reply-to logic is unchanged from the template_id path below. + let response = await fetch('https://api.mailersend.com/v1/email', { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + from, + reply_to: { + email: reply_to_email + }, + to: [{ email: to_email }], + cc: [{ email: onboarding_email }], + subject: rendered.subject, + html: rendered.html, + text: rendered.text + }) + }) + + if (!response.ok) throw new Error(`Failed: ${await response.text()}`) + console.log( + `✅ Email sent via render endpoint (rendered subject: "${rendered.subject}", sender: ${from.email})` + ) +} else { + // ------------------------------------------------------------------------------- + // Fallback path: the original template_id-based logic, unchanged. + // ------------------------------------------------------------------------------- + + // Define Template IDs + const DEFAULT_TEMPLATE = '3z0vkloo5v1l7qrx' + const SPANISH_TEMPLATE = 'o65qngkj1mjlwr12' + const UK_TEMPLATE = 'jy7zpl97rdrg5vx6' + const CANADA_EN_TEMPLATE = 'x2p0347j3r94zdrn' + const CANADA_FR_TEMPLATE = 'z86org8zmvkgew13' + // For people who did not sign up to volunteer (Intent = Act now / Keep informed). + const NOT_VOLUNTEERING_TEMPLATE = 'z86org8mdrelew13' + // UK-specific non-volunteer template (PauseAI UK, sender hello@pauseai.uk). + const UK_NON_VOLUNTEERING_TEMPLATE = 'zr6ke4nyyomgon12' + + // `Languages` is a multipleSelects field, so its value is an ARRAY (e.g. ["English","Español"]), + // and it is only ever written by the step-3 volunteer form. That form runs after this automation's + // trigger (record creation), so `languages` is empty on every send and the language branches below + // never fire: Spanish reaches its template via `country` alone, and CANADA_FR is unreachable. + const langList = Array.isArray(languages) ? languages : languages ? [String(languages)] : [] + const langLower = langList.map((l) => String(l).toLowerCase()) + + // Initial Logic: Default to English, switch if any selected language is Spanish + let template_id = DEFAULT_TEMPLATE + + if (langLower.some((l) => l.startsWith('español') || l.startsWith('spanish'))) { + template_id = SPANISH_TEMPLATE + } + + // List of Spanish-speaking countries + const spanishCountries = new Set([ + 'Argentina', + 'Bolivia', + 'Chile', + 'Colombia', + 'Costa Rica', + 'Cuba', + 'Dominican Republic', + 'Ecuador', + 'El Salvador', + 'Equatorial Guinea', + 'Guatemala', + 'Honduras', + 'Mexico', + 'Nicaragua', + 'Panama', + 'Paraguay', + 'Peru', + 'Spain', + 'Uruguay', + 'Venezuela' + ]) + + if (country) { + const cleanCountry = country.trim() + + if (spanishCountries.has(cleanCountry)) { + template_id = SPANISH_TEMPLATE + } + + if (cleanCountry.includes('United Kingdom')) { + template_id = UK_TEMPLATE + } + + // Canada Logic: Default to English unless French is explicitly selected + if (cleanCountry.includes('Canada')) { + const isFrench = langLower.some((l) => l.includes('french') || l.includes('français')) + template_id = isFrench ? CANADA_FR_TEMPLATE : CANADA_EN_TEMPLATE + } + } + + // Only `Volunteer` and `Lead` get the volunteer-framed geography templates. Everything else + // (`Act now`, `Keep informed`, an empty Intent, an unrecognised value) gets the non-volunteer + // template: it asserts nothing about why the reader signed up, so it is true for anyone, while + // the volunteer templates tell the reader they joined a volunteer network, which is false for the + // ~47% who did not. Intent therefore beats geography, and every uncertain case resolves to the + // email that cannot be wrong. + // Cost: if the `intent` mapping breaks, every record lands on the Global EN non-volunteer + // template and volunteers lose geography-specific content (the UK call invite, the ES template). + const KNOWN_INTENTS = ['Act now', 'Keep informed', 'Volunteer', 'Lead'] + const cleanIntent = String(intent || '').trim() + + // `intent` is empty when the input variable is unmapped, and also when the record itself has no + // Intent (the form always sets one; a hand-made Airtable record does not). The two are + // indistinguishable here. KNOWN_INTENTS are exact labels of an Airtable single-select option, so + // renaming one stops the match without any error. These logs are the only signal for either. + if (!cleanIntent) { + console.log( + `WARNING: Intent is empty - check the input variable mapping in the script config panel` + ) + } else if (!KNOWN_INTENTS.includes(cleanIntent)) { + console.log( + `WARNING: unrecognised Intent "${cleanIntent}" - routing to the non-volunteer template` + ) + } + + if (cleanIntent !== 'Volunteer' && cleanIntent !== 'Lead') { + template_id = NOT_VOLUNTEERING_TEMPLATE + if (country && country.trim().includes('United Kingdom')) { + template_id = UK_NON_VOLUNTEERING_TEMPLATE + } + } + + console.log( + `intent="${cleanIntent}" country="${country}" languages=${JSON.stringify(langList)} -> template ${template_id}` + ) + + // Send Email using Template + let response = await fetch('https://api.mailersend.com/v1/email', { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + reply_to: { + email: reply_to_email + }, + to: [{ email: to_email }], + cc: [{ email: onboarding_email }], + template_id: template_id, + personalization: [ + { + email: to_email, + data: { + first_name: first_name, + verification_link: verification_link + } + } + ] + }) + }) + + // Error Handling + if (!response.ok) throw new Error(`Failed: ${await response.text()}`) + console.log(`✅ Email sent using template: ${template_id}`) +} diff --git a/email-templates/ROUTING.md b/email-templates/ROUTING.md new file mode 100644 index 000000000..be16c09d3 --- /dev/null +++ b/email-templates/ROUTING.md @@ -0,0 +1,24 @@ +# Email template routing + +Script: `airtable-mailersend-emails.js` (repo root). Airtable automation → this script → MailerSend, picks a `template_id` based on `intent` / `country` / `languages`. + +Each `.json` file here = raw payload from `https://app.mailersend.com/templates//` for one template constant in the script. Fetch that URL (logged in to MailerSend) and paste the response over the stub file. Edit UI: `https://app.mailersend.com/templates//edit`. + +| Script constant | File | Routing condition | +| ------------------------------ | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | +| `DEFAULT_TEMPLATE` | `default-3z0vkloo5v1l7qrx.json` | `Volunteer`/`Lead` intent, fallback when no other rule matches (non-Spanish, non-UK, non-Canada) | +| `SPANISH_TEMPLATE` | `spanish-o65qngkj1mjlwr12.json` | `Volunteer`/`Lead` intent + `country` in Spanish-speaking list (or `languages` includes Spanish — see gap below) | +| `UK_TEMPLATE` | `uk-jy7zpl97rdrg5vx6.json` | `Volunteer`/`Lead` intent + `country` includes "United Kingdom" | +| `CANADA_EN_TEMPLATE` | `canada-en-x2p0347j3r94zdrn.json` | `Volunteer`/`Lead` intent + `country` includes "Canada", not French | +| `CANADA_FR_TEMPLATE` | `canada-fr-z86org8zmvkgew13.json` | Intended: as above + `languages` includes French — **currently unreachable, see gap below** | +| `NOT_VOLUNTEERING_TEMPLATE` | `not-volunteering-z86org8mdrelew13.json` | `intent` NOT `Volunteer`/`Lead` (i.e. `Act now`, `Keep informed`, empty, unrecognised), any country except UK | +| `UK_NON_VOLUNTEERING_TEMPLATE` | `uk-non-volunteering-zr6ke4nyyomgon12.json` | `intent` NOT `Volunteer`/`Lead` + `country` includes "United Kingdom" | + +## Known gaps / flags for whoever scopes the migration + +- **`languages` is always empty at send time.** This automation fires on record creation; `Languages` is only set later, in step 3 of the volunteer form. So: `SPANISH_TEMPLATE` is only ever reached via `country`, never via `languages`, and `CANADA_FR_TEMPLATE` is dead code — every Canadian volunteer gets the English template regardless of language. Decide whether to fix the trigger timing or drop the FR template. +- **`reply_to_email` conflict.** The script always sets `reply_to` from the Airtable `reply_to_email` input at send time — it never uses the template's own `settings.reply_to_email`. So each template's stored reply-to may not reflect what recipients actually see. Worth confirming what Airtable actually passes in per record before assuming the template's value is real. +- **Sender address isn't in the script at all** — `from` is never set on the API call, so MailerSend uses whatever `settings.sender_email`/`sender_name` each template has configured. That's the only place UK's `hello@pauseai.uk` sender identity lives — don't lose it if templates get retired. +- **Personalization payload is fixed:** script only ever sends `first_name` and `verification_link`. Any template variable outside those two won't get filled — check `variables.personalization` in each JSON for surprises. +- **UK volunteer vs UK non-volunteer** (`uk-jy7zpl97rdrg5vx6` vs `uk-non-volunteering-zr6ke4nyyomgon12`) are likely near-duplicates once both are filled in — candidate for a shared base + variant slot instead of two fully separate bodies. +- **Images**: at least one template (UK non-volunteer) has a footer image hosted on MailerSend's own CDN (`bucket.mailersendapp.com`). Those need re-hosting in the repo if templates get retired. diff --git a/email-templates/canada-en-x2p0347j3r94zdrn.json b/email-templates/canada-en-x2p0347j3r94zdrn.json new file mode 100644 index 000000000..5a0bffd18 --- /dev/null +++ b/email-templates/canada-en-x2p0347j3r94zdrn.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "x2p0347j3r94zdrn", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1008350, + "name": "New CA-EN Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "ZVB8F05tnXEWiWrHBc1RDrIBgHYZHa6a6vNOghPr.gz", + "deleted_at": null, + "created_at": "2026-04-03T15:03:08.000000Z", + "updated_at": "2026-07-15T21:44:27.000000Z", + "filename_txt": "SGmFyzhGJxb5tw7BEQi5SbwlVBkxPH47Wx3xHM1f.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_x2p0347j3r94zdrn.png", + "settings": { + "send_defaults": true, + "email_subject": "Welcome to PauseAI Canada {{first_name}}!", + "sender_name": "PauseAI", + "sender_email": "info@pauseai.info", + "reply_to_email": null, + "reply_to_name": null, + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/x2p0347j3r94zdrn/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
\n\n \n\n \n\n \n \n \n \n
\n \n \n \n \n
\n\n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n\n\n\n \n \n \n \n \n \n
\n \n \n \n \n \n \n
\n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
\n \n \n \n
\n\n\n\n\n
\n
\n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Welcome to PauseAI Canada {{first_name}}

\n

Let's Work Together to Stop the Development of Dangerous AI

\n\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

To verify your email address, click this link.

\n

If you did not request to join, you can ignore this message.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Thank you for registering with PauseAI Canada!

\n


\n

PauseAI is an international, decentralized, grassroots movement, dedicated to pausing frontier AI development until we can prove it's safe and keep it under democratic control.

\n


You can find out more about PauseAI Canada here and PauseAI Global here. Watch the video below for a summary of the Pause position:

\n\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n\n \n \n \n \n\n \n \n \n \n
\n \n \n \n \n \n
\n \n
\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
\n \n \n \n\n \n
Video Introduction
\n
\n
\n \n \n
\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
\n\n


\n

First Steps to Get Involved:

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

1. Connect With Your Community

\n

PauseAI Canada was founded in May of 2025 so we're still quite a young organization, but we're growing and eagerly seeking individuals who want to get involved. Although we don't yet have this for the PauseAI Canada website, the Global site does have a page that can guide you through ways to get involved.

\n

We have a monthly video call at 9pm ET / 8pm CT / 6pm PT on the second Wednesday. Register here (Canada). If you are in Montréal, register for the Montréal call (every third Wednesday at 7pm).

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

2. Sign Our Digital Public Statement

\n

Haven't signed yet? This statement sums up what PauseAI Global is advocating for. Sign it to take your first step pushing for the responsible development of artificial intelligence.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

How We Create Change

\n

At PauseAI, we believe in the power of collective action.

\n

By coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.

\n

As a volunteer, you'll have opportunities to participate in:

\n
  • Online actions (petitions, social media campaigns, letter-writing to officials)
  • Offline activities (local protests, community meetings, awareness events)
  • Local chapter initiatives.
\n

Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

What's Next

\n

If you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.

\n

After meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our Action page.

\n

Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

We're glad to have you on board!

\n

Jeremy Eliosoff

\n

National Leader

\n

PauseAI Canada

\n\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
\n\n \n\n\n\n\n\n\n \n \n \n \n \n
\n \n\n \n\n\n\n\n\n \n \n \n
\n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
\n\n\n\n\n
\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
\n \n \n \n \n \n
\n\n\n\n
\n\n \n \n \n \n
\n\n \n\n \n\n \n\n \n\n
\n\n
\n\n \n\n \n\n\n\n", + "plain_text": "Welcome to PauseAI Canada {{first_name}}\n\nLet's Work Together to Stop the Development of Dangerous AI\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nThank you for registering with PauseAI Canada!\n\nPauseAI is an international, decentralized, grassroots movement, dedicated to pausing frontier AI development until we can prove it's safe and keep it under democratic control.\n\nYou can find out more about PauseAI Canada [here](https://pauseai.ca/en/) and PauseAI Global [here](https://pauseai.info/). Watch the video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nFirst Steps to Get Involved:\n\n1. Connect With Your Community\n\nPauseAI Canada was founded in May of 2025 so we're still quite a young organization, but we're growing and eagerly seeking individuals who want to get involved. Although we don't yet have this for the PauseAI Canada website, the Global site does have a page that can guide you through ways to get involved.\n\nWe have a monthly video call at 9pm ET / 8pm CT / 6pm PT on the second Wednesday. [Register here](https://luma.com/calendar/cal-tsYv79s4aTQC16Q) (Canada). If you are in Montréal, [register for the Montréal call](https://luma.com/pauseaimtl) (every third Wednesday at 7pm).\n\n2. Sign Our Digital Public Statement\n\nHaven't signed yet? [This statement](https://pauseai.info/statement) sums up what PauseAI Global is advocating for. Sign it to take your first step pushing for the responsible development of artificial intelligence.\n\nHow We Create Change\n\nAt PauseAI, we believe in the power of collective action.\n\nBy coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.\n\nAs a volunteer, you'll have opportunities to participate in:\n\n- Online actions (petitions, social media campaigns, letter-writing to officials)\n- Offline activities (local protests, community meetings, awareness events)\n- Local chapter initiatives.\n\nYour participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.\n\nWhat's Next\n\nIf you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.\n\nAfter meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our [Action page](https://pauseai.info/action).\n\nWelcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nWe're glad to have you on board!\n\nJeremy Eliosoff\n\nNational Leader\n\nPauseAI Canada\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "draft_source_code": null, + "draft_plain_text": null, + "published_at": "2026-07-15T20:37:38.000000Z", + "drafted_at": null, + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 104, + "queued": 0, + "sent": 0, + "rejected": 1, + "delivered": 103, + "opened": 60, + "open_rate": "58", + "clicked": 50, + "click_rate": "49", + "unsubscribed": 0, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-21T07:07:58.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/canada-fr-z86org8zmvkgew13.json b/email-templates/canada-fr-z86org8zmvkgew13.json new file mode 100644 index 000000000..f71979947 --- /dev/null +++ b/email-templates/canada-fr-z86org8zmvkgew13.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "z86org8zmvkgew13", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1008578, + "name": "New CA-FR Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "kznDuZ5A1tzLCcuLke2hfQw6lcGYwpcDLZa2aO57.gz", + "deleted_at": null, + "created_at": "2026-04-03T21:11:47.000000Z", + "updated_at": "2026-07-15T21:44:13.000000Z", + "filename_txt": "QG3256wWCpXBWJ7PG0zMeZXh3XJC7tMXZSoliR02.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_z86org8zmvkgew13.png", + "settings": { + "send_defaults": true, + "email_subject": "Bienvenue à PauseAI Canada {{first_name}}", + "sender_name": "PauseAI", + "sender_email": "info@pauseai.info", + "reply_to_email": null, + "reply_to_name": null, + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/z86org8zmvkgew13/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
\n\n \n\n \n\n \n \n \n \n
\n \n \n \n \n
\n\n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n\n\n\n \n \n \n \n \n \n
\n \n \n \n \n \n \n
\n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
\n \n \n \n
\n\n\n\n\n
\n
\n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Bienvenue à PauseAI Canada {{first_name}}

\n

Travaillons ensemble pour arrêter le développement d’une IA dangereuse

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Pour vérifier votre adresse e-mail, cliquez sur ce lien.

\n

Si vous n'avez pas demandé à nous rejoindre, vous pouvez ignorer ce message.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n


\n

Merci de vous être inscrit à PauseAI Canada!

\n


\n


\n

PauseAI est un mouvement international, décentralisé et citoyen, qui vise à mettre le développement de l'IA de pointe sur pause jusqu'à ce que nous puissions prouver sa sécurité et le maintenir sous contrôle démocratique.

\n

Découvrez-en plus sur PauseAI Canada ici et PauseAI Global ici. Écoutez cette vidéo pour un aperçu de la position de notre position:

\n\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n\n \n \n \n \n\n \n \n \n \n
\n \n \n \n \n \n
\n \n
\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
\n \n \n \n\n \n
Introduction vidéo
\n
\n
\n \n \n
\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
\n\n


\n

Premiers pas pour vous impliquer :

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

\n

1. Connectez-vous avec votre communauté

\n\n\n

PauseAI Canada a été fondée en mai 2025. Nous sommes donc encore une jeune organisation, mais nous sommes en pleine croissance et recherchons activement des personnes souhaitant s'impliquer. Le site Global possède bien une page web qui peut vous guider sur les façons de vous impliquer.

\n

Nous organisons un appel vidéo mensuel chaque second mercredi du mois à 21 h 00 HE. S’inscrire pour joindre l’appel CANADA. Si vous êtes à Montréal, nous avons un appel chaque troisième mercredi du mois à 19 h 00 HE. S’inscrire pour joindre l’appel MONTRÉAL.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

\n

Comment nous faisons bouger les choses

\n\n\n

Chez PauseAI, nous croyons au pouvoir de l'action collective.

\n

En nous rassemblant en tant que citoyens engagés pour manifester, sensibiliser le public et écrire à nos décideurs, nous pouvons influencer le changement nécessaire afin de plaider en faveur d'une pause dans le développement des technologies d'IA les plus avancées.

\n

En tant que bénévole, vous aurez l'occasion de participer à :

\n
  • Des actions en ligne (pétitions, campagnes sur les réseaux sociaux, envoi de lettres aux responsables)

  • Des activités hors ligne (manifestations locales, réunions communautaires, événements de sensibilisation)

  • Initiatives des sections locales.

\n

Votre participation, quelle que soit son ampleur, joue un rôle essentiel dans notre effort collectif visant à garantir que le développement de l'IA se déroule de manière sûre et éthique.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Et maintenant ?

\n\n

Si vous avez choisi de vous abonner, vous recevrez l'infolettre mensuelle de PauseAI, qui vous tiendra informé des actions et événements à venir.

\n

Après avoir rencontré la communauté lors de nos réunions de bienvenue ou par l'intermédiaire de votre section nationale, vous serez informé de la prochaine action à mener, mais si vous souhaitez agir dès maintenant, consultez notre page « Action » (de PauseIA France pour le moment).

\n

Bienvenue parmi nous ! Ensemble, nous pouvons agir pour prévenir les conséquences catastrophiques du développement de l'intelligence artificielle

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

\n

Nous sommes heureux de vous avoir avec nous!

\n


Jeremy Eliosoff

\n

National Leader

\n

PauseAI Canada

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
\n\n \n\n\n\n\n\n\n \n \n \n \n \n
\n \n\n \n\n\n\n\n\n \n \n \n
\n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
\n\n\n\n\n
\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
\n \n \n \n \n \n
\n\n\n\n
\n\n \n \n \n \n
\n\n \n\n \n\n \n\n \n\n
\n\n
\n\n \n\n \n\n\n\n", + "plain_text": "Bienvenue à PauseAI Canada {{first_name}}\n\nTravaillons ensemble pour arrêter le développement d’une IA dangereuse\n\nPour vérifier votre adresse e-mail, [cliquez sur ce lien]({{verification_link}}).\n\nSi vous n'avez pas demandé à nous rejoindre, vous pouvez ignorer ce message.\n\nMerci de vous être inscrit à PauseAI Canada!\n\nPauseAI est un mouvement international, décentralisé et citoyen, qui vise à mettre le développement de l'IA de pointe sur pause jusqu'à ce que nous puissions prouver sa sécurité et le maintenir sous contrôle démocratique.\n\nDécouvrez-en plus sur PauseAI Canada [ici](https://pauseai.ca/en/) et PauseAI Global [ici](https://pauseai.info/). Écoutez cette vidéo pour un aperçu de la position de notre position:\n\n[Introduction vidéo](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nPremiers pas pour vous impliquer :\n\n1. Connectez-vous avec votre communauté\n\nPauseAI Canada a été fondée en mai 2025. Nous sommes donc encore une jeune organisation, mais nous sommes en pleine croissance et recherchons activement des personnes souhaitant s'impliquer. Le site Global possède bien une page web qui peut vous guider sur les façons de vous impliquer.\n\nNous organisons un appel vidéo mensuel chaque second mercredi du mois à 21 h 00 HE. [S’inscrire pour joindre l’appel CANADA](https://luma.com/calendar/cal-tsYv79s4aTQC16Q). Si vous êtes à Montréal, nous avons un appel chaque troisième mercredi du mois à 19 h 00 HE. [S’inscrire pour joindre l’appel MONTRÉAL](https://luma.com/pauseaimtl).\n\nComment nous faisons bouger les choses\n\nChez PauseAI, nous croyons au pouvoir de l'action collective.\n\nEn nous rassemblant en tant que citoyens engagés pour manifester, sensibiliser le public et écrire à nos décideurs, nous pouvons influencer le changement nécessaire afin de plaider en faveur d'une pause dans le développement des technologies d'IA les plus avancées.\n\nEn tant que bénévole, vous aurez l'occasion de participer à :\n\n-\n\nDes actions en ligne (pétitions, campagnes sur les réseaux sociaux, envoi de lettres aux responsables)\n\n-\n\nDes activités hors ligne (manifestations locales, réunions communautaires, événements de sensibilisation)\n\n-\n\nInitiatives des sections locales.\n\nVotre participation, quelle que soit son ampleur, joue un rôle essentiel dans notre effort collectif visant à garantir que le développement de l'IA se déroule de manière sûre et éthique.\n\nEt maintenant ?\n\nSi vous avez choisi de vous abonner, vous recevrez l'infolettre mensuelle de PauseAI, qui vous tiendra informé des actions et événements à venir.\n\nAprès avoir rencontré la communauté lors de nos réunions de bienvenue ou par l'intermédiaire de votre section nationale, vous serez informé de la prochaine action à mener, mais si vous souhaitez agir dès maintenant, consultez notre page « Action » ([de PauseIA France pour le moment](https://pauseia.fr/fr/agir)).\n\nBienvenue parmi nous ! Ensemble, nous pouvons agir pour prévenir les conséquences catastrophiques du développement de l'intelligence artificielle\n\nNous sommes heureux de vous avoir avec nous!\n\nJeremy Eliosoff\n\nNational Leader\n\nPauseAI Canada\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "draft_source_code": null, + "draft_plain_text": null, + "published_at": "2026-07-15T20:43:20.000000Z", + "drafted_at": null, + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 0, + "queued": 0, + "sent": 0, + "rejected": 0, + "delivered": 0, + "opened": 0, + "open_rate": 0, + "clicked": 0, + "click_rate": 0, + "unsubscribed": 0, + "unsubscribe_rate": 0, + "complained": 0, + "complain_rate": 0, + "last_email_sent_at": null + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/default-3z0vkloo5v1l7qrx.json b/email-templates/default-3z0vkloo5v1l7qrx.json new file mode 100644 index 000000000..1674cc0b0 --- /dev/null +++ b/email-templates/default-3z0vkloo5v1l7qrx.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "3z0vkloo5v1l7qrx", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1007688, + "name": "New EN Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "AMOIOJRqvBfRzbh1zcNchOgltGFEdkYI1MjS6vMq.gz", + "deleted_at": null, + "created_at": "2026-04-02T17:50:49.000000Z", + "updated_at": "2026-07-15T21:45:29.000000Z", + "filename_txt": "n6kWLNlKJYTwDY3zRpse9QQNBqFJLcPFNenZjPni.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_3z0vkloo5v1l7qrx.png", + "settings": { + "send_defaults": true, + "email_subject": "Welcome to PauseAI Global {{first_name}}!", + "sender_name": "PauseAI", + "sender_email": "info@pauseai.info", + "reply_to_email": null, + "reply_to_name": null, + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/3z0vkloo5v1l7qrx/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
\n\n \n\n \n\n \n \n \n \n
\n \n \n \n \n
\n\n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n\n\n\n \n \n \n \n \n \n
\n \n \n \n \n \n \n
\n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
\n \n \n \n
\n\n\n\n\n
\n
\n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

Welcome to PauseAI Global {{first_name}}

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

To verify your email address, click this link.

\n

If you did not request to join, you can ignore this message.

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n \n \n \n \n
\n
\n\n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

We're thrilled to have you join our growing global volunteer network. Your decision to stand with us demonstrates that you share our commitment to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n \n \n\n \n \n \n \n\n \n \n \n \n
\n \n \n \n \n \n
\n \n
\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
\n \n \n \n\n \n
Video Introduction
\n
\n
\n \n \n
\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
\n\n

First Steps to Get Involved:

\n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
\n \n \n \n \n \n
\n\n\n \n \n \n \n \n \n
\n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
\n\n

1. Join Our Welcome Calls

\n

Join us for an introduction to the PauseAI community. You'll meet other new volunteers, learn about our current initiatives, and find ways to get involved immediately. Check out our list of upcoming welcome calls here.

\n\n
    \n\n\n\n
    \n

    2. Connect With Your Community

    \n

    Please join us on our global Discord server.

    \n

    You will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.

    \n

    If no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    How We Create Change

    \n

    At PauseAI, we believe in the power of collective action.

    \n

    By coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.

    \n

    As a volunteer, you'll have opportunities to participate in:

    \n
    • Online actions (petitions, social media campaigns, letter-writing to officials)
    • Offline activities (local protests, community meetings, awareness events)
    • Local chapter initiatives.
    \n

    Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    What's Next

    \n

    If you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.

    \n

    After meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our Action page.

    \n

    Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    Best regards,

    \n

    Maxime and The PauseAI Global Team

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n \n \n \n \n
    \n \n \n \n \n
    \n
    \n\n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
    \n\n \n\n\n\n\n\n\n \n \n \n \n \n
    \n \n\n \n\n\n\n\n\n \n \n \n
    \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
    \n\n\n\n\n
    \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
    \n \n \n \n \n \n
    \n\n\n\n
    \n\n \n \n \n \n
    \n\n \n\n \n\n \n\n \n\n
    \n\n
    \n\n \n\n \n\n\n\n", + "plain_text": "Welcome to PauseAI Global {{first_name}}\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nWe're thrilled to have you join our growing global volunteer network. Your decision to stand with us demonstrates that you share our commitment to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nFirst Steps to Get Involved:\n\n1. Join Our Welcome Calls\n\nJoin us for an introduction to the PauseAI community. You'll meet other new volunteers, learn about our current initiatives, and find ways to get involved immediately. Check out our list of upcoming welcome calls [here](https://luma.com/PauseAI?tag=welcome).\n\n2. Connect With Your Community\n\nPlease join us on our global [Discord server](https://discord.gg/gTymKVFs7Z).\n\nYou will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.\n\nIf no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.\n\nHow We Create Change\n\nAt PauseAI, we believe in the power of collective action.\n\nBy coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.\n\nAs a volunteer, you'll have opportunities to participate in:\n\n- Online actions (petitions, social media campaigns, letter-writing to officials)\n- Offline activities (local protests, community meetings, awareness events)\n- Local chapter initiatives.\n\nYour participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.\n\nWhat's Next\n\nIf you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.\n\nAfter meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our [Action page](https://pauseai.info/action).\n\nWelcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nBest regards,\n\nMaxime and The PauseAI Global Team\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "draft_source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
    \n\n \n\n \n\n \n \n \n \n
    \n \n \n \n \n
    \n\n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n \n \n \n\n\n\n \n \n \n \n \n \n
    \n \n \n \n \n \n \n
    \n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
    \n \n \n \n
    \n\n\n\n\n
    \n
    \n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    Welcome to PauseAI Global {{first_name}}

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n \n \n \n \n
    \n \n \n \n \n
    \n
    \n\n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    To verify your email address, click this link.

    \n

    If you did not request to join, you can ignore this message.

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n \n \n \n \n
    \n \n \n \n \n
    \n
    \n\n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    We're thrilled to have you join our growing global volunteer network. Your decision to stand with us demonstrates that you share our commitment to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n \n \n\n \n \n \n \n\n \n \n \n \n
    \n \n \n \n \n \n
    \n \n
    \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
    \n \n \n \n\n \n
    Video Introduction
    \n
    \n
    \n \n \n
    \n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
    \n\n

    First Steps to Get Involved:

    \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
    \n \n \n \n \n \n
    \n\n\n \n \n \n \n \n \n
    \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
    \n\n

    1. Join Our Welcome Calls

    \n

    Join us for an introduction to the PauseAI community. You'll meet other new volunteers, learn about our current initiatives, and find ways to get involved immediately. Check out our list of upcoming welcome calls here.

    \n\n
      \n\n\n\n
      \n

      2. Connect With Your Community

      \n

      Please join us on our global Discord server.

      \n

      You will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.

      \n

      If no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      How We Create Change

      \n

      At PauseAI, we believe in the power of collective action.

      \n

      By coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.

      \n

      As a volunteer, you'll have opportunities to participate in:

      \n
      • Online actions (petitions, social media campaigns, letter-writing to officials)
      • Offline activities (local protests, community meetings, awareness events)
      • Local chapter initiatives.
      \n

      Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      What's Next

      \n

      You’re now on our mailing list and will receive the PauseAI monthly newsletter which will update you on upcoming actions and events.

      \n

      After meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our Action page.

      \n

      Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      Best regards,

      \n

      Maxime and The PauseAI Global Team

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n \n \n \n \n
      \n \n \n \n \n
      \n
      \n\n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
      \n\n \n\n\n\n\n\n\n \n \n \n \n \n
      \n \n\n \n\n\n\n\n\n \n \n \n
      \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
      \n\n\n\n\n
      \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
      \n \n \n \n \n \n
      \n\n\n\n
      \n\n \n \n \n \n
      \n\n \n\n \n\n \n\n \n\n
      \n\n
      \n\n \n\n \n\n\n\n", + "draft_plain_text": "Welcome to PauseAI Global {{first_name}}\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nWe're thrilled to have you join our growing global volunteer network. Your decision to stand with us demonstrates that you share our commitment to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nFirst Steps to Get Involved:\n\n1. Join Our Welcome Calls\n\nJoin us for an introduction to the PauseAI community. You'll meet other new volunteers, learn about our current initiatives, and find ways to get involved immediately. Check out our list of upcoming welcome calls [here](https://luma.com/PauseAI?tag=welcome).\n\n2. Connect With Your Community\n\nPlease join us on our global [Discord server](https://discord.gg/gTymKVFs7Z).\n\nYou will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.\n\nIf no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.\n\nHow We Create Change\n\nAt PauseAI, we believe in the power of collective action.\n\nBy coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.\n\nAs a volunteer, you'll have opportunities to participate in:\n\n- Online actions (petitions, social media campaigns, letter-writing to officials)\n- Offline activities (local protests, community meetings, awareness events)\n- Local chapter initiatives.\n\nYour participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.\n\nWhat's Next\n\nYou’re now on our mailing list and will receive the PauseAI monthly newsletter which will update you on upcoming actions and events.\n\nAfter meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our [Action page](https://pauseai.info/action).\n\nWelcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nBest regards,\n\nMaxime and The PauseAI Global Team\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "published_at": "2026-07-15T20:36:51.000000Z", + "drafted_at": null, + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 1300, + "queued": 1, + "sent": 0, + "rejected": 17, + "delivered": 1282, + "opened": 777, + "open_rate": "61", + "clicked": 443, + "click_rate": "35", + "unsubscribed": 3, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-25T11:59:25.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/not-volunteering-z86org8mdrelew13.json b/email-templates/not-volunteering-z86org8mdrelew13.json new file mode 100644 index 000000000..524392653 --- /dev/null +++ b/email-templates/not-volunteering-z86org8mdrelew13.json @@ -0,0 +1,88 @@ +{ + "data": { + "id": "z86org8mdrelew13", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1076095, + "name": "New EN Non-Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "6k3C3CeGc13WduZFzxeN5zn9ygK46gzuGuaDOJPO.gz", + "deleted_at": null, + "created_at": "2026-07-15T20:52:53.000000Z", + "updated_at": "2026-07-27T05:28:43.000000Z", + "filename_txt": "dYKAc50xUk0Z2pWw2zdn76pCNn42zYD2QWpIce1k.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_z86org8mdrelew13.png", + "settings": { + "send_defaults": true, + "email_subject": "Welcome to PauseAI Global {{first_name}}!", + "sender_name": "PauseAI", + "sender_email": "info@pauseai.info", + "reply_to_email": null, + "reply_to_name": null, + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/z86org8mdrelew13/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
      \n\n \n\n \n\n \n \n \n \n
      \n \n \n \n \n
      \n\n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n \n \n \n\n\n\n \n \n \n \n \n \n
      \n \n \n \n \n \n \n
      \n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
      \n \n \n \n
      \n\n\n\n\n
      \n
      \n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      Welcome to PauseAI Global {{first_name}}

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n \n \n \n \n
      \n \n \n \n \n
      \n
      \n\n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      To verify your email address, click this link.

      \n

      If you did not request to join, you can ignore this message.

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n \n \n \n \n
      \n \n \n \n \n
      \n
      \n\n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      Thanks for signing up. We're committed to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n \n \n\n \n \n \n \n\n \n \n \n \n
      \n \n \n \n \n \n
      \n \n
      \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
      \n \n \n \n\n \n
      Video Introduction
      \n
      \n
      \n \n \n
      \n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
      \n\n

      First Steps to Get Involved:

      \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
      \n \n \n \n \n \n
      \n\n\n \n \n \n \n \n \n
      \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
      \n\n

      1. Join Our Welcome Calls

      \n

      Join one of our Member Community Welcome Meetings to meet others who have just joined, hear more about what we do, and find ways to get involved. Check out our list of upcoming welcome calls here.

      \n
        \n\n\n\n
        \n

        2. Connect With Your Community

        \n

        Please join us on our global Discord server.

        \n

        You will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.

        \n

        If no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        How We Create Change

        \n

        At PauseAI, we believe in the power of collective action.

        \n

        By coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.

        \n

        There are lots of ways to take part:

        \n
        • Online actions (petitions, social media campaigns, letter-writing to officials)
        • Offline activities (local protests, community meetings, awareness events)
        • Local chapter initiatives.
        \n

        Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        What's Next

        \n

        If you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.

        \n

        After meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our Action page.

        \n

        Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        Best regards,

        \n

        Maxime and The PauseAI Global Team

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n \n \n \n \n
        \n \n \n \n \n
        \n
        \n\n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
        \n\n \n\n\n\n\n\n\n \n \n \n \n \n
        \n \n\n \n\n\n\n\n\n \n \n \n
        \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
        \n\n\n\n\n
        \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
        \n \n \n \n \n \n
        \n\n\n\n
        \n\n \n \n \n \n
        \n\n \n\n \n\n \n\n \n\n
        \n\n
        \n\n \n\n \n\n\n\n", + "plain_text": "Welcome to PauseAI Global {{first_name}}\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nThanks for signing up. We're committed to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nFirst Steps to Get Involved:\n\n1. Join Our Welcome Calls\n\nJoin one of our Member Community Welcome Meetings to meet others who have just joined, hear more about what we do, and find ways to get involved. Check out our list of upcoming welcome calls [here](https://luma.com/PauseAI?tag=welcome).\n\n2. Connect With Your Community\n\nPlease join us on our global [Discord server](https://discord.gg/gTymKVFs7Z).\n\nYou will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.\n\nIf no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.\n\nHow We Create Change\n\nAt PauseAI, we believe in the power of collective action.\n\nBy coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.\n\nThere are lots of ways to take part:\n\n- Online actions (petitions, social media campaigns, letter-writing to officials)\n- Offline activities (local protests, community meetings, awareness events)\n- Local chapter initiatives.\n\nYour participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.\n\nWhat's Next\n\nIf you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.\n\nAfter meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our [Action page](https://pauseai.info/action).\n\nWelcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nBest regards,\n\nMaxime and The PauseAI Global Team\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "draft_source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
        \n\n \n\n \n\n \n \n \n \n
        \n \n \n \n \n
        \n\n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n \n \n \n\n\n\n \n \n \n \n \n \n
        \n \n \n \n \n \n \n
        \n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
        \n \n \n \n
        \n\n\n\n\n
        \n
        \n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        Welcome to PauseAI Global {{first_name}}

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n \n \n \n \n
        \n \n \n \n \n
        \n
        \n\n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        To verify your email address, click this link.

        \n

        If you did not request to join, you can ignore this message.

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n \n \n \n \n
        \n \n \n \n \n
        \n
        \n\n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        Thanks for signing up. We're committed to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n \n \n\n \n \n \n \n\n \n \n \n \n
        \n \n \n \n \n \n
        \n \n
        \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
        \n \n \n \n\n \n
        Video Introduction
        \n
        \n
        \n \n \n
        \n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
        \n\n

        First Steps to Get Involved:

        \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
        \n \n \n \n \n \n
        \n\n\n \n \n \n \n \n \n
        \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
        \n\n

        1. Join Our Welcome Calls

        \n

        Join one of our Member Community Welcome Meetings to meet others who have just joined, hear more about what we do, and find ways to get involved. Check out our list of upcoming welcome calls here.

        \n
          \n\n\n\n
          \n

          2. Connect With Your Community

          \n

          Please join us on our global Discord server.

          \n

          You will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.

          \n

          If no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          How We Create Change

          \n

          At PauseAI, we believe in the power of collective action.

          \n

          By coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.

          \n

          There are lots of ways to take part:

          \n
          • Online actions (petitions, social media campaigns, letter-writing to officials)
          • Offline activities (local protests, community meetings, awareness events)
          • Local chapter initiatives.
          \n

          Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          What's Next

          \n

          If you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.

          \n

          After meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our Action page.

          \n

          Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          Best regards,

          \n

          Maxime and The PauseAI Global Team

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n \n \n \n \n
          \n \n \n \n \n
          \n
          \n\n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
          \n\n \n\n\n\n\n\n\n \n \n \n \n \n
          \n \n\n \n\n\n\n\n\n \n \n \n
          \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
          \n\n\n\n\n
          \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
          \n \n \n \n \n \n
          \n\n\n\n
          \n\n \n \n \n \n
          \n\n \n\n \n\n \n\n \n\n
          \n\n
          \n\n \n\n \n\n\n\n", + "draft_plain_text": "Welcome to PauseAI Global {{first_name}}\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nThanks for signing up. We're committed to ensuring artificial intelligence is developed slowly and safely in a way that benefits all of humanity, and isn't left to the whims of for-profit companies in a reckless race to smarter-than-human AI. Watch our video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nFirst Steps to Get Involved:\n\n1. Join Our Welcome Calls\n\nJoin one of our Member Community Welcome Meetings to meet others who have just joined, hear more about what we do, and find ways to get involved. Check out our list of upcoming welcome calls [here](https://luma.com/PauseAI?tag=welcome).\n\n2. Connect With Your Community\n\nPlease join us on our global [Discord server](https://discord.gg/gTymKVFs7Z).\n\nYou will receive an email or Discord message from the Chapter Lead of your country inviting you to meet your PauseAI community at meetings and events.\n\nIf no Chapter exists in your community, you will receive an email or Discord message from our Global Onboarding team.\n\nHow We Create Change\n\nAt PauseAI, we believe in the power of collective action.\n\nBy coming together as concerned citizens to protest, persuade the public, and write to our decision-makers we can influence the necessary change to advocate for a pause on the most advanced AI development.\n\nThere are lots of ways to take part:\n\n- Online actions (petitions, social media campaigns, letter-writing to officials)\n- Offline activities (local protests, community meetings, awareness events)\n- Local chapter initiatives.\n\nYour participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.\n\nWhat's Next\n\nIf you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.\n\nAfter meeting with the community via our Welcome meetings or through your National Chapter, you’ll be informed of the next action, but if you’re looking for a step to take right now, check out our [Action page](https://pauseai.info/action).\n\nWelcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nBest regards,\n\nMaxime and The PauseAI Global Team\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "published_at": "2026-07-15T21:34:16.000000Z", + "drafted_at": "2026-07-27T05:28:43.000000Z", + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 419, + "queued": 0, + "sent": 4, + "rejected": 1, + "delivered": 414, + "opened": 254, + "open_rate": "61", + "clicked": 109, + "click_rate": "26", + "unsubscribed": 1, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-25T08:13:51.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/spanish-o65qngkj1mjlwr12.json b/email-templates/spanish-o65qngkj1mjlwr12.json new file mode 100644 index 000000000..fa30ad73f --- /dev/null +++ b/email-templates/spanish-o65qngkj1mjlwr12.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "o65qngkj1mjlwr12", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1007651, + "name": "New ES Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "IyDC6GF31cnVBVLpFWVHcUkBqj8NJLS0QSLZnjqR.gz", + "deleted_at": null, + "created_at": "2026-04-02T17:10:52.000000Z", + "updated_at": "2026-07-16T12:29:00.000000Z", + "filename_txt": "rRVXPjDXYa3tXdk0FOT8r2qvtm2ijGgzvX4oDzB4.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_o65qngkj1mjlwr12.png", + "settings": { + "send_defaults": true, + "email_subject": "Bienvenido a PauseAI {{first_name}}!", + "sender_name": "PauseAI", + "sender_email": "info@pauseai.info", + "reply_to_email": null, + "reply_to_name": null, + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/o65qngkj1mjlwr12/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
          \n\n \n\n \n\n \n \n \n \n
          \n \n \n \n \n
          \n\n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n \n \n \n\n\n\n \n \n \n \n \n \n
          \n \n \n \n \n \n \n
          \n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
          \n \n \n \n
          \n\n\n\n\n
          \n
          \n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          ¡Bienvenido a PauseAI {{first_name}}!

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n \n \n \n \n
          \n \n \n \n \n
          \n
          \n\n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          Para verificar tu dirección de email, haz clic en este link.

          \n

          Si no solicitaste unirte, puedes ignorar este mensaje.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n \n \n \n \n
          \n \n \n \n \n
          \n
          \n\n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          Estamos encantados de que te unas a nuestra creciente red global de voluntarios. Tu decisión de apoyarnos demuestra que compartes nuestro compromiso de garantizar que la inteligencia artificial se desarrolle de forma pausada y segura, beneficiando a todos en lugar de quedar a merced de los caprichos de empresas con fines de lucro en una carrera imprudente hacia una IA más inteligente que la humana. Mira nuestro vídeo a continuación para obtener un resumen de la posición de PauseAI:

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n \n \n\n \n \n \n \n\n \n \n \n \n
          \n \n \n \n \n \n
          \n \n
          \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
          \n \n \n \n\n \n
          Video Introductorio
          \n
          \n
          \n \n \n
          \n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
          \n\n

          Primeros pasos para involucrarse en las comunidades:

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          PauseAI en Español

          \n

          Voluntarios de España e Hispanoamérica nos hemos unido bajo el nombre de PauseAI en Español, con el objetivo de paliar la falta de información sobre el tema en nuestro idioma y desarrollar iniciativas útiles.

          \n

          Contamos con un WhatsApp donde coordinamos llamadas grupales mensuales y una página web propia. En la web encontrarás una sección de artículos en profundidad sobre los riesgos de la IA, información sobre la necesidad de una pausa y consejos para escribir a los políticos, entre otros contenidos. También tenemos un Substack en español donde publicamos un boletín mensual de noticias.

          \n

          Con el apoyo de voluntarios como tú esperamos poner en marcha muchos más proyectos de comunicación y activismo, estamos abiertos a tus sugerencias.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          PauseAI Global

          \n

          Por fuera de eso, puedes unirte a nuestro servidor de Discord y las llamadas de bienvenida para personas de cualquier parte del mundo. Ambas en inglés.

          \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
          \n \n \n \n \n \n
          \n\n\n \n \n \n \n \n \n
          \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
          \n\n

          Cómo creamos el cambio

          \n

          En PauseAI creemos en el poder de la acción colectiva.

          \n

          Al unirnos como ciudadanos preocupados para protestar, persuadir al público y escribir a quienes toman las decisiones, podemos influir en el cambio necesario para abogar por una pausa en el desarrollo más avanzado de la IA.

          \n

          Como voluntario, tendrás oportunidades de participar en:

          \n
          • Acciones online: peticiones, campañas en redes sociales, envío de emails a funcionarios.
          • Actividades presenciales: protestas locales, reuniones comunitarias, eventos de concientización.
          • Iniciativas de los capítulos locales.
          \n

          Tu participación, ya sea grande o pequeña, es muy importante en nuestro esfuerzo colectivo para garantizar que el desarrollo de la IA avance de forma segura y ética.

          \n
            \n
              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              ¿Y ahora qué?

              \n

              Si te suscribiste a nuestra lista de correo, recibirás el boletín mensual de PauseAI que te mantendrá al día sobre las próximas acciones y eventos.

              \n

              Después de reunirte con la comunidad a través de nuestras reuniones de bienvenida o a través de tu Capítulo nacional, se te informará sobre la próxima acción, pero si estás buscando un paso a seguir ahora mismo, consulta nuestra página de Acción.

              \n

              ¡Bienvenido a bordo! Juntos, podemos tomar medidas para prevenir los impactos catastróficos del desarrollo de la Inteligencia Artificial.

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              Saludos cordiales,

              \n

              Maxime y el equipo de PauseAI Global

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n \n \n
              \n \n \n \n \n
              \n
              \n\n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
              \n\n \n\n\n\n\n\n\n \n \n \n \n \n
              \n \n\n \n\n\n\n\n\n \n \n \n
              \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
              \n\n\n\n\n
              \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
              \n \n \n \n \n \n
              \n\n\n\n
              \n\n \n \n \n \n
              \n\n \n\n \n\n \n\n \n\n
              \n\n
              \n\n \n\n \n\n\n\n", + "plain_text": "¡Bienvenido a PauseAI {{first_name}}!\n\nPara verificar tu dirección de email, haz clic en [este link]({{verification_link}}).\n\nSi no solicitaste unirte, puedes ignorar este mensaje.\n\nEstamos encantados de que te unas a nuestra creciente red global de voluntarios. Tu decisión de apoyarnos demuestra que compartes nuestro compromiso de garantizar que la inteligencia artificial se desarrolle de forma pausada y segura, beneficiando a todos en lugar de quedar a merced de los caprichos de empresas con fines de lucro en una carrera imprudente hacia una IA más inteligente que la humana. Mira nuestro vídeo a continuación para obtener un resumen de la posición de PauseAI:\n\n[Video Introductorio](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nPrimeros pasos para involucrarse en las comunidades:\n\nPauseAI en Español\n\nVoluntarios de España e Hispanoamérica nos hemos unido bajo el nombre de PauseAI en Español, con el objetivo de paliar la falta de información sobre el tema en nuestro idioma y desarrollar iniciativas útiles.\n\nContamos con un [WhatsApp](https://chat.whatsapp.com/KEgD22LEo6xEVvjH4fD8br)donde coordinamos llamadas grupales mensuales y una página [web propia](https://pauseai.es/). En la web encontrarás una sección de artículos en profundidad sobre [los riesgos de la IA](https://pauseai.es/riesgos), información sobre la necesidad de una pausa y consejos para escribir a los políticos, entre otros contenidos. También tenemos un [Substack en español](https://pauseaispanish.substack.com/) donde publicamos un boletín mensual de noticias.\n\nCon el apoyo de voluntarios como tú esperamos poner en marcha muchos más proyectos de comunicación y activismo, estamos abiertos a tus sugerencias.\n\nPauseAI Global\n\nPor fuera de eso, puedes unirte a nuestro [servidor de Discord](https://discord.gg/gTymKVFs7Z)y las [llamadas de bienvenida](https://luma.com/PauseAI?tag=welcome) para personas de cualquier parte del mundo. Ambas en inglés.\n\nCómo creamos el cambio\n\nEn PauseAI creemos en el poder de la acción colectiva.\n\nAl unirnos como ciudadanos preocupados para protestar, persuadir al público y escribir a quienes toman las decisiones, podemos influir en el cambio necesario para abogar por una pausa en el desarrollo más avanzado de la IA.\n\nComo voluntario, tendrás oportunidades de participar en:\n\n- Acciones online: peticiones, campañas en redes sociales, envío de emails a funcionarios.\n- Actividades presenciales: protestas locales, reuniones comunitarias, eventos de concientización.\n- Iniciativas de los capítulos locales.\n\nTu participación, ya sea grande o pequeña, es muy importante en nuestro esfuerzo colectivo para garantizar que el desarrollo de la IA avance de forma segura y ética.\n\n¿Y ahora qué?\n\nSi te suscribiste a nuestra lista de correo, recibirás el boletín mensual de PauseAI que te mantendrá al día sobre las próximas acciones y eventos.\n\nDespués de reunirte con la comunidad a través de nuestras reuniones de bienvenida o a través de tu Capítulo nacional, se te informará sobre la próxima acción, pero si estás buscando un paso a seguir ahora mismo, consulta nuestra página de [Acción](https://pauseai.info/action).\n\n¡Bienvenido a bordo! Juntos, podemos tomar medidas para prevenir los impactos catastróficos del desarrollo de la Inteligencia Artificial.\n\nSaludos cordiales,\n\nMaxime y el equipo de PauseAI Global\n\n[youtube](https://www.youtube.com/@PauseAI)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pause_ai/)\t[x](https://twitter.com/PauseAI)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://tiktok.com/@pauseai)\t[facebook](https://www.facebook.com/PauseAI/)", + "draft_source_code": null, + "draft_plain_text": null, + "published_at": "2026-07-16T12:29:00.000000Z", + "drafted_at": null, + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 61, + "queued": 0, + "sent": 0, + "rejected": 0, + "delivered": 61, + "opened": 34, + "open_rate": "56", + "clicked": 25, + "click_rate": "41", + "unsubscribed": 0, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-14T11:25:39.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/uk-jy7zpl97rdrg5vx6.json b/email-templates/uk-jy7zpl97rdrg5vx6.json new file mode 100644 index 000000000..4f039e848 --- /dev/null +++ b/email-templates/uk-jy7zpl97rdrg5vx6.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "jy7zpl97rdrg5vx6", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1007659, + "name": "New UK Volunteer Welcome", + "description": "", + "type": "dd", + "filename": "5WbPg1OdYNQZO3qcylUzxk6aB6sV2YwnldBF1MES.gz", + "deleted_at": null, + "created_at": "2026-04-02T17:14:48.000000Z", + "updated_at": "2026-07-15T21:45:55.000000Z", + "filename_txt": "NJh8ZaVozszA9YNMi33hje5BMfS1Pc9cXXBbYjr6.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_jy7zpl97rdrg5vx6.png", + "settings": { + "send_defaults": true, + "email_subject": "Welcome to PauseAI UK {{first_name}}!", + "sender_name": "PauseAI UK", + "sender_email": "info@pauseai.info", + "reply_to_email": "joseph@pauseai.info", + "reply_to_name": "Joseph Miller", + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/jy7zpl97rdrg5vx6/preview", + "source_code": "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n \n \n \n \n\n \n\n \n \n\n \n \n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n\n
              \n\n \n\n \n\n \n \n \n \n
              \n \n \n \n \n
              \n\n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n \n\n\n\n \n \n \n \n \n \n
              \n \n \n \n \n \n \n
              \n \n\n \n\n\n\n\n\n\n\"\"\n\n\n\n\n\n\n \n \n \n\n \n\n\n\n \n \n \n
              \n \n \n \n
              \n\n\n\n\n
              \n
              \n\n\n\n\n\n \n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              Welcome to PauseAI UK, {{first_name}}.

              \n

              Let's work together to stop the development of dangerous AI

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n \n \n
              \n \n \n \n \n
              \n
              \n\n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              To verify your email address, click this link.

              \n

              If you did not request to join, you can ignore this message.

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n \n \n
              \n \n \n \n \n
              \n
              \n\n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              We're thrilled to have you join our growing volunteer network. We are committed to ensuring that artificial intelligence is developed slowly and safely in a way that benefits all of humanity. And that it isn't left to the whims of companies in a reckless race to build AI that can replace humans. Watch our video below for a summary of the Pause position:

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n\n \n \n \n \n\n \n \n \n \n
              \n \n \n \n \n \n
              \n \n
              \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n
              \n \n \n \n\n \n
              Video Introduction
              \n
              \n
              \n \n \n
              \n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n\n\n \n\n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              Connect With Your Community

              \n

              Join the PauseAI UK WhatsApp community to keep up with our events.

              \n

              You can also join the PauseAI Global Discord server to meet the international PauseAI community.

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              Next steps

              \n

              Please book a 10 minute call with Joseph, the Director of PauseAI UK, so he can say hi and introduce you to the community. Or reply to this email if you have a question.

              \n

              You’re now on our mailing list and will receive the PauseAI monthly newsletter. New events will be announced in the WhatsApp community.

              \n

              Welcome aboard! At PauseAI, we believe in the power of collective action. Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n

              Best wishes,

              \n

              Joseph and Matilda, The PauseAI UK Team

              \n\n \n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n \n \n
              \n\n \n \n \n \n
              \n \n \n \n \n
              \n
              \n\n \n\n\n\n\n
              \n \n \n \n \n \n
              \n\n\n \n \n \n \n
              \n \n \n \n\n \n \n\n \n \n \n \n \n \n \n \n
              \n\n \n\n\n\n\n\n\n \n \n \n \n \n
              \n \n\n \n\n\n\n\n\n \n \n \n
              \n \n \"youtube\"\n \n \n \n \"discord\"\n \n \n \n \"instagram\"\n \n \n \n \"x\"\n \n \n \n \"bluesky\"\n \n \n \n \"tiktok\"\n \n \n \n \"facebook\"\n \n
              \n\n\n\n\n
              \n\n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n \n\n\n\n \n \n \n \n\n\n\n \n \n\n\n\n\n\n \n \n \n\n\n\n
              \n \n \n \n \n \n
              \n\n\n\n
              \n\n \n \n \n \n
              \n\n \n\n \n\n \n\n \n\n
              \n\n
              \n\n \n\n \n\n\n", + "plain_text": "Welcome to PauseAI UK, {{first_name}}.\n\nLet's work together to stop the development of dangerous AI\n\nTo verify your email address, click [this link]({{verification_link}}).\n\nIf you did not request to join, you can ignore this message.\n\nWe're thrilled to have you join our growing volunteer network. We are committed to ensuring that artificial intelligence is developed slowly and safely in a way that benefits all of humanity. And that it isn't left to the whims of companies in a reckless race to build AI that can replace humans. Watch our video below for a summary of the Pause position:\n\n[Video Introduction](https://www.youtube.com/watch?v=ZHxJwv4TdJo)\n\nConnect With Your Community\n\nJoin the [PauseAI UK WhatsApp community](https://chat.whatsapp.com/BesRzGOwnfNHuQM9fLIr0J) to keep up with our events.\n\nYou can also join the PauseAI Global [Discord server](https://discord.gg/gTymKVFs7Z) to meet the international PauseAI community.\n\nNext steps\n\nPlease [book a 10 minute call](https://calendar.app.google/w5t7EgCFwCGKcnAS7) with Joseph, the Director of PauseAI UK, so he can say hi and introduce you to the community. Or reply to this email if you have a question.\n\nYou’re now on our mailing list and will receive the PauseAI monthly newsletter. New events will be announced in the [WhatsApp community](https://chat.whatsapp.com/BesRzGOwnfNHuQM9fLIr0J).\n\nWelcome aboard! At PauseAI, we believe in the power of collective action. Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.\n\nBest wishes,\n\n[Joseph](mailto:joseph@pauseai.info) and [Matilda](mailto:matilda@pauseai.info), The PauseAI UK Team\n\n[youtube](https://www.youtube.com/@PauseAI-UK)\t[discord](https://discord.gg/2XXWXvErfA)\t[instagram](https://www.instagram.com/pauseai_uk)\t[x](https://x.com/pauseai_uk)\t[bluesky](https://bsky.app/profile/pauseai.bsky.social)\t[tiktok](https://www.tiktok.com/@pauseai_uk)\t[facebook](https://www.facebook.com/profile.php?id=61587358827177)", + "draft_source_code": null, + "draft_plain_text": null, + "published_at": "2026-04-28T14:58:51.000000Z", + "drafted_at": null, + "split_testing": true, + "split_testing_enabled": true, + "split_testing_enabled_at": "2026-04-28T15:47:22.000000Z", + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 326, + "queued": 0, + "sent": 0, + "rejected": 3, + "delivered": 323, + "opened": 115, + "open_rate": "36", + "clicked": 84, + "click_rate": "26", + "unsubscribed": 0, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-25T07:55:57.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/email-templates/uk-non-volunteering-zr6ke4nyyomgon12.json b/email-templates/uk-non-volunteering-zr6ke4nyyomgon12.json new file mode 100644 index 000000000..625f210a7 --- /dev/null +++ b/email-templates/uk-non-volunteering-zr6ke4nyyomgon12.json @@ -0,0 +1,85 @@ +{ + "data": { + "id": "zr6ke4nyyomgon12", + "account_id": "0p7kx4xzq1vg9yjr", + "builder_id": 1082941, + "name": "UK Non-Volunteer Welcome", + "description": "", + "type": "rt", + "filename": "krmcaeDCyr1VQeD4kXwMBbWEWrWNAZCaHpV1JzDa.gz", + "deleted_at": null, + "created_at": "2026-07-27T05:24:12.000000Z", + "updated_at": "2026-07-28T07:47:45.000000Z", + "filename_txt": "nSdUUmRPNoLInLDQi4EchcIlRci7lJwQaOA6iDg4.gz", + "image_path": "https://storage.googleapis.com/mailersend-screenshots/screenshots/template_zr6ke4nyyomgon12.png", + "settings": { + "send_defaults": true, + "email_subject": "Welcome to PauseAI UK {{first_name}}!", + "sender_name": "PauseAI UK", + "sender_email": "hello@pauseai.uk", + "reply_to_email": "hello@pauseai.uk", + "reply_to_name": "PauseAI UK", + "tags": [], + "test_data": "{}", + "test_data_draft": "{}", + "auto_generate": true + }, + "preview_url": "https://app.mailersend.com/templates/zr6ke4nyyomgon12/preview", + "source_code": "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n \n\n\n\n\n \n\n \n \n \n \n
              \n\n \n\n \n\n \n\n \n\n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n \n \n \n \n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n\n\n\n\n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n\n\n\n\n\n\n\n\n\n\n \n \n \n \n
              \n\n \n
              \n

              Hey {{ first_name }},

              \n\n
              \n\n
              \n

              Welcome to our community!

              Here's how you can get involved in PauseAI UK:

              1. Confirm your email address so that we can confirm it’s really you signing up

              \n\n
              \n\n
              \n

              2. Join the PauseAI UK WhatsApp community and subscribe to our UK events calendar.

              \n\n
              \n\n
              \n

              3. If you'd like, book a short call with Joseph, PauseAI UK's director, so he can say hi and introduce you to the community. Looking forward to meeting you,

              \n\n
              \n\n
              \n

              Joseph and Matilda, the PauseAI UK Team

              \n\n
              \n\n
              \n

              PS: if you have questions, just hit reply.

              \n\n
              \n\n
              \n


              \n\n
              \n\n \n\n\n\n\n \n \n \n\n \n\n \n \n \n \n \n \n \n \n\n \n\n \n \n\n \n\n \n \n \n \n
              \n\n \n
              \n \n\n\n\n\n
              \n\n \n \n \n \n
              \n\n\n\n\n\n \n\n
              \n\n\n\n", + "plain_text": "Hey {{ first_name }},\n\nWelcome to our community!\n\nHere's how you can get involved in PauseAI UK:\n\n1. [Confirm your email address]({{verification_link}}) so that we can confirm it’s really you signing up\n\n2. Join the [PauseAI UK WhatsApp community](https://chat.whatsapp.com/F0nj2RjLNeB1P1hyoDFsTz) and subscribe to our [UK events calendar.](https://luma.com/pauseai.uk)\n\n3. If you'd like, [book a short call](https://calendar.app.google/w5t7EgCFwCGKcnAS7) with Joseph, PauseAI UK's director, so he can say hi and introduce you to the community. Looking forward to meeting you,\n\n[Joseph](mailto:joseph@pauseai.uk) and [Matilda](mailto:matilda@pauseai.uk), the PauseAI UK Team\n\nPS: if you have questions, just hit reply.", + "draft_source_code": null, + "draft_plain_text": null, + "published_at": "2026-07-28T07:47:45.000000Z", + "drafted_at": null, + "split_testing": false, + "split_testing_enabled": false, + "split_testing_enabled_at": null, + "split_main": false, + "split_parent_id": "", + "template_stats": { + "total": 94, + "queued": 0, + "sent": 0, + "rejected": 0, + "delivered": 94, + "opened": 32, + "open_rate": "34", + "clicked": 24, + "click_rate": "26", + "unsubscribed": 0, + "unsubscribe_rate": "0", + "complained": 0, + "complain_rate": "0", + "last_email_sent_at": "2026-08-25T16:14:50.000000Z" + }, + "variables": { + "variables": [], + "personalization": { + "first_name": "", + "verification_link": "" + } + }, + "draft_variables": { + "variables": [], + "personalization": [] + }, + "compile_errors": { + "base": null, + "draft": null, + "subject": null, + "base_txt": null, + "draft_txt": null, + "encoding_error": null, + "original_filename": null, + "simple_personalization_variables": null + }, + "can": { + "manage": true, + "createVersion": true, + "createSplitTest": false + }, + "split_winner_id": null, + "has_survey": false, + "origin": "app" + } +} diff --git a/src/lib/server/onboardingEmail/blocks.ts b/src/lib/server/onboardingEmail/blocks.ts new file mode 100644 index 000000000..25e4c115d --- /dev/null +++ b/src/lib/server/onboardingEmail/blocks.ts @@ -0,0 +1,95 @@ +import type { ChapterLink, IntentBucket, OnboardingEmailParams } from './types.js' +import type { LanguageCopy } from './copy.js' +import { SHARED } from './copy.js' +import type { ChapterBlockData } from './types.js' + +export type EmailBlock = + | { type: 'heading'; text: string } + | { type: 'paragraph'; text: string } + | { type: 'list'; items: string[] } + | { type: 'links'; items: ChapterLink[] } + +const KNOWN_INTENTS = new Set(['Act now', 'Keep informed', 'Volunteer', 'Lead']) + +/** Maps the raw Airtable `Intent` value onto one of the 4 copy variants, falling + * back to `keep-informed` for empty/unrecognised values — matching the old + * script's "every uncertain case resolves to the [non-volunteer] email" rule. */ +export function resolveIntentBucket(intent: string | undefined): IntentBucket { + const clean = (intent ?? '').trim() + if (!KNOWN_INTENTS.has(clean)) return 'keep-informed' + switch (clean) { + case 'Act now': + return 'act-now' + case 'Volunteer': + return 'volunteer' + case 'Lead': + return 'lead' + default: + return 'keep-informed' + } +} + +function isVolunteering(bucket: IntentBucket): boolean { + return bucket === 'volunteer' || bucket === 'lead' +} + +export function buildEmailBlocks( + params: OnboardingEmailParams, + copy: LanguageCopy, + chapter: ChapterBlockData, + verificationLink: string, + bucket: IntentBucket +): EmailBlock[] { + const volunteering = isVolunteering(bucket) + const welcomeCallsUrl = + chapter.links.find((l) => l.label.startsWith('Events'))?.url ?? + 'https://luma.com/PauseAI?tag=welcome' + + const blocks: EmailBlock[] = [ + { type: 'heading', text: copy.greeting(params.firstName) }, + { type: 'paragraph', text: copy.verifyLine(verificationLink) }, + { type: 'paragraph', text: copy.ignoreNote }, + { type: 'paragraph', text: copy.intent[bucket] }, + { + type: 'paragraph', + text: `${copy.videoIntro} [${copy.videoLinkText}](${SHARED.VIDEO_URL})` + }, + { type: 'heading', text: copy.connectHeading }, + { + type: 'paragraph', + text: chapter.isGlobalFallback + ? copy.connectFallbackIntro + : copy.connectFoundIntro(chapter.name, chapter.leader) + }, + { type: 'paragraph', text: copy.welcomeCallsLine(welcomeCallsUrl) } + ] + + if (chapter.links.length > 0) { + blocks.push({ type: 'links', items: chapter.links }) + } + + blocks.push( + { type: 'heading', text: copy.howWeCreateChangeHeading }, + { type: 'paragraph', text: copy.howWeCreateChangeIntro }, + { + type: 'paragraph', + text: volunteering + ? copy.howWeCreateChangeVolunteerLine + : copy.howWeCreateChangeNonVolunteerLine + }, + { type: 'list', items: copy.howWeCreateChangeListItems }, + { type: 'paragraph', text: copy.howWeCreateChangeClosing }, + { type: 'heading', text: copy.whatsNextHeading }, + { type: 'paragraph', text: copy.whatsNextP1 }, + { type: 'paragraph', text: copy.whatsNextP2 }, + { type: 'paragraph', text: copy.whatsNextClosing }, + { + type: 'paragraph', + text: chapter.isGlobalFallback + ? copy.signoffFallback + : copy.signoffFound(chapter.leader, chapter.name) + } + ) + + return blocks +} diff --git a/src/lib/server/onboardingEmail/chapter.ts b/src/lib/server/onboardingEmail/chapter.ts new file mode 100644 index 000000000..b7d91e3de --- /dev/null +++ b/src/lib/server/onboardingEmail/chapter.ts @@ -0,0 +1,99 @@ +import type { AirtableNationalGroup } from '$lib/types.js' +import { fetchAllPages, type AirtableRecord } from '$lib/airtable.js' +import type { ChapterBlockData, ChapterLink } from './types.js' + +// Same base/table/filter as src/routes/api/national-groups/+server.ts. Reusing the +// underlying fetch logic in-process (rather than an HTTP call to our own API) per the +// architecture decision record. +const AIRTABLE_URL = 'https://api.airtable.com/v0/appWPTGqZmUcs3NWu/tblEQJ26hxBAEkaP8' + +// Same PauseAI Bluesky account appears in every old template's footer, including +// UK's (which otherwise swaps every other social handle for its own branded +// account) - confirmed global, not per-chapter. No `bluesky` field exists on +// NationalGroup/Airtable, so this is fixed rather than looked up. +const BLUESKY_LINK: ChapterLink = { + label: 'Bluesky', + url: 'https://bsky.app/profile/pauseai.bsky.social' +} + +// Hardcoded fallback used when `country` has no matching National Groups record, +// matching the signoff/links email-templates/default-*.json and +// email-templates/not-volunteering-*.json currently use. +const GLOBAL_FALLBACK: ChapterBlockData = { + isGlobalFallback: true, + name: 'Global', + leader: 'Maxime', + links: [ + { label: 'Discord', url: 'https://discord.gg/gTymKVFs7Z' }, + { label: 'Welcome calls', url: 'https://luma.com/PauseAI?tag=welcome' }, + { label: 'YouTube', url: 'https://www.youtube.com/@PauseAI' }, + { label: 'Instagram', url: 'https://www.instagram.com/pause_ai/' }, + { label: 'X', url: 'https://twitter.com/PauseAI' }, + BLUESKY_LINK, + { label: 'TikTok', url: 'https://tiktok.com/@pauseai' }, + { label: 'Facebook', url: 'https://www.facebook.com/PauseAI/' } + ] +} + +// Field -> display label, in the fixed order links should render. +const LINK_FIELDS: { field: keyof AirtableNationalGroup; label: string }[] = [ + { field: 'website', label: 'Website' }, + { field: 'discord', label: 'Discord' }, + { field: 'whatsapp', label: 'WhatsApp' }, + { field: 'luma', label: 'Events (Luma)' }, + { field: 'substack', label: 'Substack' }, + { field: 'x', label: 'X' }, + { field: 'instagram', label: 'Instagram' }, + { field: 'tiktok', label: 'TikTok' }, + { field: 'facebook', label: 'Facebook' }, + { field: 'youtube', label: 'YouTube' }, + { field: 'linkedin', label: 'LinkedIn' }, + { field: 'linktree', label: 'Linktree' } +] + +function normalizeCountry(country: string): string { + return country.trim().toLowerCase() +} + +function recordToChapterBlock(record: AirtableRecord): ChapterBlockData { + const leaderNames = (record.fields.leaders_name ?? []).map((n) => n.trim()).filter(Boolean) + const leader = leaderNames.length > 0 ? leaderNames.join(' and ') : GLOBAL_FALLBACK.leader + + const links: ChapterLink[] = LINK_FIELDS.map(({ field, label }) => { + const value = record.fields[field] + return { label, url: typeof value === 'string' ? value.trim() : '' } + }).filter((link) => link.url.length > 0) + + const xIndex = links.findIndex((link) => link.label === 'X') + links.splice(xIndex === -1 ? links.length : xIndex + 1, 0, BLUESKY_LINK) + + return { + isGlobalFallback: false, + name: (record.fields.country ?? '').trim(), + leader, + links + } +} + +/** + * Looks up the live National Groups record matching `country` (exact, case-insensitive + * match against the record's own free-text `country` field — the same key + * src/routes/api/national-groups/+server.ts exposes as `NationalGroup.name`). Falls back + * to the hardcoded global chapter when there's no match or `country` is empty. + */ +export async function getChapterForOnboardingEmail( + country: string | undefined +): Promise { + const cleanCountry = (country ?? '').trim() + if (!cleanCountry) return GLOBAL_FALLBACK + + const records = await fetchAllPages(fetch, AIRTABLE_URL, [], { + filterByFormula: 'NOT({inactive})' + }) + + const match = records.find( + (record) => normalizeCountry(record.fields.country ?? '') === normalizeCountry(cleanCountry) + ) + + return match ? recordToChapterBlock(match) : GLOBAL_FALLBACK +} diff --git a/src/lib/server/onboardingEmail/copy.ts b/src/lib/server/onboardingEmail/copy.ts new file mode 100644 index 000000000..690c92c71 --- /dev/null +++ b/src/lib/server/onboardingEmail/copy.ts @@ -0,0 +1,206 @@ +import type { IntentBucket, OnboardingEmailLanguage } from './types.js' + +// Hand-maintained en/es/fr copy for the onboarding welcome email. NOT routed through +// the site's Paraglide/inlang pipeline (build-time only, not live in prod — see +// docs/L10N.md). Adapted from the org's existing reviewed copy in +// email-templates/*.json (see ROUTING.md for which template each block came from), +// not invented from scratch, EXCEPT where a comment below says otherwise. +// +// Markdown-style `[label](url)` links are used throughout (matching the `plain_text` +// style already used in the existing MailerSend templates) and are converted to +// `` tags for HTML by src/lib/server/onboardingEmail/markdown.ts. + +const VIDEO_URL = 'https://www.youtube.com/watch?v=ZHxJwv4TdJo' +const ACTION_PAGE_URL = 'https://pauseai.info/action' + +export type LanguageCopy = { + subject: (firstName: string) => string + greeting: (firstName: string) => string + verifyLine: (verificationLink: string) => string + ignoreNote: string + /** Intent-acknowledgement sentence, echoing the join-form copy in + * src/lib/components/onboarding/messages.ts (~L204-217). */ + intent: Record + videoIntro: string + videoLinkText: string + howWeCreateChangeHeading: string + howWeCreateChangeIntro: string + /** Differs slightly depending on whether the reader opted into volunteering. */ + howWeCreateChangeVolunteerLine: string + howWeCreateChangeNonVolunteerLine: string + howWeCreateChangeListItems: string[] + howWeCreateChangeClosing: string + connectHeading: string + connectFoundIntro: (chapterName: string, leader: string) => string + connectFallbackIntro: string + welcomeCallsLine: (url: string) => string + whatsNextHeading: string + whatsNextP1: string + whatsNextP2: string + whatsNextClosing: string + signoffFound: (leader: string, chapterName: string) => string + signoffFallback: string + linksHeading: string + unsubscribeLine: (url: string) => string + addressLine: string +} + +const en: LanguageCopy = { + subject: (firstName) => `Welcome to PauseAI, ${firstName}!`, + greeting: (firstName) => `Welcome to PauseAI, ${firstName}!`, + verifyLine: (link) => `To verify your email address, click [this link](${link}).`, + ignoreNote: 'If you did not request to join, you can ignore this message.', + intent: { + 'keep-informed': + "You told us you want to keep informed — you'll get global campaign updates, plus news and ways to help from your local chapter.", + 'act-now': "You told us you just want to take action now — here's what you can do today.", + volunteer: + "You told us you want to volunteer regularly — we'll help you find a role that fits, and your local chapter will be in touch.", + lead: "You told us you want to lead — you're ready to organize in your country or region, and your local chapter will be in touch." + }, + videoIntro: 'Watch our video below for a summary of the Pause position:', + videoLinkText: 'Video Introduction', + howWeCreateChangeHeading: 'How We Create Change', + howWeCreateChangeIntro: 'At PauseAI, we believe in the power of collective action.', + howWeCreateChangeVolunteerLine: "As a volunteer, you'll have opportunities to participate in:", + howWeCreateChangeNonVolunteerLine: 'There are lots of ways to take part:', + howWeCreateChangeListItems: [ + 'Online actions (petitions, social media campaigns, letter-writing to officials)', + 'Offline activities (local protests, community meetings, awareness events)', + 'Local chapter initiatives.' + ], + howWeCreateChangeClosing: + 'Your participation, whether big or small, matters greatly in our collective effort to ensure AI development proceeds safely and ethically.', + connectHeading: 'Connect With Your Community', + connectFoundIntro: (chapterName, leader) => + `You're part of the PauseAI ${chapterName} chapter, led by ${leader}. They'll be in touch to help you get plugged in locally.`, + connectFallbackIntro: + "There's no local PauseAI chapter in your country yet, so for now you're part of the PauseAI Global community. Our Global Onboarding team will be in touch.", + welcomeCallsLine: (url) => + `In the meantime, join one of our [Welcome Calls](${url}) to meet other new members and hear more about what we do.`, + whatsNextHeading: "What's Next", + whatsNextP1: + "If you opted in to our newsletter, you'll receive the PauseAI monthly update on upcoming actions and events.", + whatsNextP2: `After meeting with the community via our Welcome meetings or through your National Chapter, you'll be informed of the next action, but if you're looking for a step to take right now, check out our [Action page](${ACTION_PAGE_URL}).`, + whatsNextClosing: + 'Welcome aboard! Together, we can take action to prevent the catastrophic impacts of the development of Artificial Intelligence.', + signoffFound: (leader, chapterName) => `${leader} and the PauseAI ${chapterName} Team`, + signoffFallback: 'Maxime and The PauseAI Global Team', + linksHeading: 'Follow along', + unsubscribeLine: (url) => `Don't want to receive these emails? [Unsubscribe here](${url}).`, + addressLine: 'PauseAI, Box C5957, Kwikstaartlaan 42, 3704GS Zeist, The Netherlands' +} + +// Adapted from email-templates/canada-fr-*.json, with Canada/Montréal-specific +// content and the pauseia.fr/fr/agir stand-in link dropped (France is now a real +// chapter reached via the normal lookup — see ROUTING.md and the decision record). +// Register: "vous" throughout, matching that template's tone — NOT the "tu" register +// used on the live /join form's fr copy in messages.ts. The intent-acknowledgement +// sentences below were translated from messages.ts's "tu" copy into "vous" to keep +// the whole email in one consistent register; flagging this register choice for +// human review since it means the email doesn't literally quote the join-form copy. +const fr: LanguageCopy = { + subject: (firstName) => `Bienvenue à PauseAI, ${firstName} !`, + greeting: (firstName) => `Bienvenue à PauseAI, ${firstName} !`, + verifyLine: (link) => `Pour vérifier votre adresse e-mail, [cliquez sur ce lien](${link}).`, + ignoreNote: "Si vous n'avez pas demandé à nous rejoindre, vous pouvez ignorer ce message.", + intent: { + 'keep-informed': + "Vous nous avez dit vouloir rester informé·e — vous recevrez les actualités des campagnes mondiales, ainsi que les nouvelles et les occasions d'agir de votre groupe local.", + 'act-now': + "Vous nous avez dit vouloir agir dès maintenant — voici ce que vous pouvez faire dès aujourd'hui.", + volunteer: + 'Vous nous avez dit vouloir être bénévole régulièrement — nous allons vous aider à trouver un rôle qui vous correspond, et votre groupe local vous contactera.', + lead: 'Vous nous avez dit vouloir prendre la tête — vous êtes prêt·e à organiser dans votre pays ou votre région, et votre groupe local vous contactera.' + }, + videoIntro: 'Regardez notre vidéo ci-dessous pour un résumé de la position de PauseAI :', + videoLinkText: 'Introduction vidéo', + howWeCreateChangeHeading: 'Comment nous faisons bouger les choses', + howWeCreateChangeIntro: "Chez PauseAI, nous croyons au pouvoir de l'action collective.", + howWeCreateChangeVolunteerLine: "En tant que bénévole, vous aurez l'occasion de participer à :", + howWeCreateChangeNonVolunteerLine: 'Il existe de nombreuses façons de participer :', + howWeCreateChangeListItems: [ + 'Des actions en ligne (pétitions, campagnes sur les réseaux sociaux, envoi de lettres aux responsables)', + 'Des activités hors ligne (manifestations locales, réunions communautaires, événements de sensibilisation)', + 'Des initiatives des sections locales.' + ], + howWeCreateChangeClosing: + "Votre participation, quelle que soit son ampleur, joue un rôle essentiel dans notre effort collectif visant à garantir que le développement de l'IA se déroule de manière sûre et éthique.", + connectHeading: 'Connectez-vous avec votre communauté', + connectFoundIntro: (chapterName, leader) => + `Vous faites partie de la section PauseAI ${chapterName}, dirigée par ${leader}. Elle vous contactera pour vous aider à vous impliquer localement.`, + connectFallbackIntro: + "Il n'existe pas encore de section locale de PauseAI dans votre pays, vous faites donc partie de la communauté PauseAI Global pour l'instant. Notre équipe d'accueil mondiale vous contactera.", + welcomeCallsLine: (url) => + `En attendant, rejoignez l'un de nos [appels de bienvenue](${url}) pour rencontrer d'autres nouveaux membres et en savoir plus sur ce que nous faisons.`, + whatsNextHeading: 'Et maintenant ?', + whatsNextP1: + "Si vous avez choisi de vous abonner, vous recevrez l'infolettre mensuelle de PauseAI, qui vous tiendra informé des actions et événements à venir.", + whatsNextP2: `Après avoir rencontré la communauté lors de nos réunions de bienvenue ou par l'intermédiaire de votre section nationale, vous serez informé de la prochaine action à mener, mais si vous souhaitez agir dès maintenant, consultez notre page [Action](${ACTION_PAGE_URL}).`, + whatsNextClosing: + "Bienvenue parmi nous ! Ensemble, nous pouvons agir pour prévenir les conséquences catastrophiques du développement de l'intelligence artificielle.", + signoffFound: (leader, chapterName) => `${leader} et l'équipe PauseAI ${chapterName}`, + signoffFallback: "Maxime et l'équipe PauseAI Global", + linksHeading: 'Suivez-nous', + unsubscribeLine: (url) => + `Vous ne souhaitez plus recevoir ces e-mails ? [Se désabonner ici](${url}).`, + addressLine: 'PauseAI, Box C5957, Kwikstaartlaan 42, 3704GS Zeist, Pays-Bas' +} + +// Adapted from email-templates/spanish-*.json for the shared sections. The intent +// acknowledgement sentences, the fallback/found "connect with your community" copy, +// and the non-volunteer variant of "how we create change" have NO equivalent in any +// existing template or in messages.ts (which has no `es` locale) — these are +// newly-written Spanish copy and should get native-speaker review before shipping. +const es: LanguageCopy = { + subject: (firstName) => `¡Bienvenido/a a PauseAI, ${firstName}!`, + greeting: (firstName) => `¡Bienvenido/a a PauseAI, ${firstName}!`, + verifyLine: (link) => `Para verificar tu dirección de email, haz clic en [este enlace](${link}).`, + ignoreNote: 'Si no solicitaste unirte, puedes ignorar este mensaje.', + intent: { + 'keep-informed': + 'Nos dijiste que quieres mantenerte informado/a — recibirás actualizaciones de campañas globales, además de noticias y formas de ayudar desde tu capítulo local.', + 'act-now': + 'Nos dijiste que solo quieres pasar a la acción ahora — esto es lo que puedes hacer hoy mismo.', + volunteer: + 'Nos dijiste que quieres ser voluntario/a de forma regular — te ayudaremos a encontrar un rol que encaje contigo, y tu capítulo local se pondrá en contacto.', + lead: 'Nos dijiste que quieres liderar — estás listo/a para organizar en tu país o región, y tu capítulo local se pondrá en contacto.' + }, + videoIntro: + 'Mira nuestro vídeo a continuación para obtener un resumen de la posición de PauseAI:', + videoLinkText: 'Video Introductorio', + howWeCreateChangeHeading: 'Cómo creamos el cambio', + howWeCreateChangeIntro: 'En PauseAI creemos en el poder de la acción colectiva.', + howWeCreateChangeVolunteerLine: 'Como voluntario/a, tendrás oportunidades de participar en:', + howWeCreateChangeNonVolunteerLine: 'Hay muchas maneras de participar:', + howWeCreateChangeListItems: [ + 'Acciones online: peticiones, campañas en redes sociales, envío de emails a funcionarios.', + 'Actividades presenciales: protestas locales, reuniones comunitarias, eventos de concientización.', + 'Iniciativas de los capítulos locales.' + ], + howWeCreateChangeClosing: + 'Tu participación, ya sea grande o pequeña, es muy importante en nuestro esfuerzo colectivo para garantizar que el desarrollo de la IA avance de forma segura y ética.', + connectHeading: 'Conecta con tu comunidad', + connectFoundIntro: (chapterName, leader) => + `Formas parte del capítulo de PauseAI en ${chapterName}, liderado por ${leader}. Se pondrán en contacto contigo para ayudarte a integrarte a nivel local.`, + connectFallbackIntro: + 'Todavía no hay un capítulo local de PauseAI en tu país, así que por ahora formas parte de la comunidad global de PauseAI. Nuestro equipo global de incorporación se pondrá en contacto contigo.', + welcomeCallsLine: (url) => + `Mientras tanto, únete a una de nuestras [llamadas de bienvenida](${url}) para conocer a otros nuevos miembros y saber más sobre lo que hacemos.`, + whatsNextHeading: '¿Y ahora qué?', + whatsNextP1: + 'Si te suscribiste a nuestra lista de correo, recibirás el boletín mensual de PauseAI que te mantendrá al día sobre las próximas acciones y eventos.', + whatsNextP2: `Después de reunirte con la comunidad a través de nuestras reuniones de bienvenida o a través de tu Capítulo nacional, se te informará sobre la próxima acción, pero si estás buscando un paso a seguir ahora mismo, consulta nuestra página de [Acción](${ACTION_PAGE_URL}).`, + whatsNextClosing: + '¡Bienvenido/a a bordo! Juntos, podemos tomar medidas para prevenir los impactos catastróficos del desarrollo de la Inteligencia Artificial.', + signoffFound: (leader, chapterName) => `${leader} y el equipo de PauseAI ${chapterName}`, + signoffFallback: 'Maxime y el equipo de PauseAI Global', + linksHeading: 'Síguenos', + unsubscribeLine: (url) => + `¿No quieres recibir estos correos? [Cancelar suscripción aquí](${url}).`, + addressLine: 'PauseAI, Box C5957, Kwikstaartlaan 42, 3704GS Zeist, Países Bajos' +} + +export const LANGUAGE_COPY: Record = { en, fr, es } + +export const SHARED = { VIDEO_URL, ACTION_PAGE_URL } diff --git a/src/lib/server/onboardingEmail/html.ts b/src/lib/server/onboardingEmail/html.ts new file mode 100644 index 000000000..2084f43cf --- /dev/null +++ b/src/lib/server/onboardingEmail/html.ts @@ -0,0 +1,95 @@ +import type { LanguageCopy } from './copy.js' +import type { EmailBlock } from './blocks.js' +import { escapeHtml, mdLineToHtml } from './markdown.js' + +// Matches the site's brand orange (--hero-orange in src/styles/styles.css), also the +// accent color used throughout the existing MailerSend template exports. +const ACCENT = '#ff9416' +const TEXT = '#222222' +const MUTED = '#6b6b6b' +const BORDER = '#e5e5e5' +const BODY_BG = '#f6f6f6' +const CARD_BG = '#ffffff' + +function renderBlock(block: EmailBlock): string { + switch (block.type) { + case 'heading': + return `${mdLineToHtml(block.text, ACCENT)}` + case 'paragraph': + return `${mdLineToHtml(block.text, ACCENT)}` + case 'list': { + const items = block.items + .map( + (item) => + `
            • ${mdLineToHtml(item, ACCENT)}
            • ` + ) + .join('') + return `
                ${items}
              ` + } + case 'links': { + const links = block.items + .map( + (item) => + `
              ${escapeHtml(item.label)}` + ) + .join('  ·  ') + return `${links}` + } + } +} + +/** Hand-rolled table-based HTML, matching the general inline-styles/table-layout + * approach of the existing MailerSend exports (email-templates/*.json) rather than + * modern CSS, for email-client compatibility. No logo image is used (avoids a + * broken-image risk for a first version) — just a text wordmark. */ +export function renderHtml( + blocks: EmailBlock[], + copy: LanguageCopy, + unsubscribeUrl: string +): string { + const rows = blocks.map(renderBlock).join('') + + return ` + + + + + +PauseAI + + + + + + +
              + + + + + + + + + + +
              +PauseAI +
              + +${rows} +
              +
              + + + + + + + +
              ${mdLineToHtml(copy.unsubscribeLine(unsubscribeUrl), MUTED)}
              ${escapeHtml(copy.addressLine)}
              +
              +
              + +` +} diff --git a/src/lib/server/onboardingEmail/index.ts b/src/lib/server/onboardingEmail/index.ts new file mode 100644 index 000000000..f9a718181 --- /dev/null +++ b/src/lib/server/onboardingEmail/index.ts @@ -0,0 +1,44 @@ +import { url, verificationParameter } from '$lib/config.js' +import { getChapterForOnboardingEmail } from './chapter.js' +import { resolveOnboardingEmailLanguage } from './language.js' +import { LANGUAGE_COPY } from './copy.js' +import { buildEmailBlocks, resolveIntentBucket } from './blocks.js' +import { renderHtml } from './html.js' +import { renderText } from './text.js' +import type { OnboardingEmailParams, RenderedOnboardingEmail } from './types.js' + +export type { + OnboardingEmailParams, + RenderedOnboardingEmail, + OnboardingEmailLanguage +} from './types.js' + +/** + * Renders the full volunteer-onboarding welcome email (subject + html + text) for a + * given signup, matrixed on intent x chapter x language. See + * ~/.claude/projects/-Users-harryturnbull-pauseai-website/memory/project-email-template-redesign.md + * for the architecture decision record this implements. + */ +export async function renderOnboardingEmail( + params: OnboardingEmailParams +): Promise { + if (!params.airtable_id) { + throw new Error('airtable_id is required to build verification/unsubscribe links') + } + + const verificationLink = `${url}/verify?table=join&${verificationParameter}=${params.airtable_id}` + const unsubscribeLink = `${url}/api/unsubscribe?${verificationParameter}=${params.airtable_id}` + + const language = resolveOnboardingEmailLanguage(params.country, params.languages) + const copy = LANGUAGE_COPY[language] + const bucket = resolveIntentBucket(params.intent) + const chapter = await getChapterForOnboardingEmail(params.country) + + const blocks = buildEmailBlocks(params, copy, chapter, verificationLink, bucket) + + return { + subject: copy.subject(params.firstName), + html: renderHtml(blocks, copy, unsubscribeLink), + text: renderText(blocks, copy, unsubscribeLink) + } +} diff --git a/src/lib/server/onboardingEmail/language.ts b/src/lib/server/onboardingEmail/language.ts new file mode 100644 index 000000000..0e4ae061b --- /dev/null +++ b/src/lib/server/onboardingEmail/language.ts @@ -0,0 +1,68 @@ +import type { OnboardingEmailLanguage } from './types.js' + +// Mirrors the Spanish-speaking country list and Canada/French detection logic in +// airtable-mailersend-emails.js exactly, so language routing doesn't drift between +// the render endpoint and the (fallback) template_id script. +const SPANISH_COUNTRIES = new Set([ + 'Argentina', + 'Bolivia', + 'Chile', + 'Colombia', + 'Costa Rica', + 'Cuba', + 'Dominican Republic', + 'Ecuador', + 'El Salvador', + 'Equatorial Guinea', + 'Guatemala', + 'Honduras', + 'Mexico', + 'Nicaragua', + 'Panama', + 'Paraguay', + 'Peru', + 'Spain', + 'Uruguay', + 'Venezuela' +]) + +function normalizeLanguages(languages: string[] | string | undefined): string[] { + const list = Array.isArray(languages) ? languages : languages ? [String(languages)] : [] + return list.map((l) => String(l).toLowerCase()) +} + +/** + * Resolves which hand-maintained email language to use, primarily from `country`, + * matching the current airtable-mailersend-emails.js routing: + * - any selected language starting with "español"/"spanish" -> es + * - country in the Spanish-speaking list -> es + * - country is France -> fr + * - country is Canada + French explicitly selected in `languages` -> fr + * - everything else -> en + */ +export function resolveOnboardingEmailLanguage( + country: string | undefined, + languages: string[] | string | undefined +): OnboardingEmailLanguage { + const langLower = normalizeLanguages(languages) + const cleanCountry = (country ?? '').trim() + + if (langLower.some((l) => l.startsWith('español') || l.startsWith('spanish'))) { + return 'es' + } + + if (cleanCountry && SPANISH_COUNTRIES.has(cleanCountry)) { + return 'es' + } + + if (cleanCountry.includes('France')) { + return 'fr' + } + + if (cleanCountry.includes('Canada')) { + const isFrench = langLower.some((l) => l.includes('french') || l.includes('français')) + if (isFrench) return 'fr' + } + + return 'en' +} diff --git a/src/lib/server/onboardingEmail/markdown.ts b/src/lib/server/onboardingEmail/markdown.ts new file mode 100644 index 000000000..3798d73d0 --- /dev/null +++ b/src/lib/server/onboardingEmail/markdown.ts @@ -0,0 +1,27 @@ +// Minimal markdown-link support: the copy in copy.ts uses `[label](url)`, matching +// the style already used in the existing MailerSend templates' `plain_text` fields +// (see email-templates/*.json). Plain text output uses that syntax verbatim; HTML +// output converts it to a real `` tag. + +const LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/g + +export function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +/** + * Escapes `text` for safe HTML output, then converts `[label](url)` markdown + * links into `` tags. The URL itself is escaped but not otherwise validated — + * callers control every URL that reaches this (chapter links, static copy). + */ +export function mdLineToHtml(text: string, linkColor: string): string { + const escaped = escapeHtml(text) + return escaped.replace(LINK_PATTERN, (_match, label: string, url: string) => { + return `${label}` + }) +} diff --git a/src/lib/server/onboardingEmail/text.ts b/src/lib/server/onboardingEmail/text.ts new file mode 100644 index 000000000..89e8ad9c1 --- /dev/null +++ b/src/lib/server/onboardingEmail/text.ts @@ -0,0 +1,34 @@ +import type { LanguageCopy } from './copy.js' +import type { EmailBlock } from './blocks.js' + +/** Renders blocks + footer to plain text, matching the `[label](url)` markdown-link + * style already used in the existing templates' `plain_text` fields. */ +export function renderText( + blocks: EmailBlock[], + copy: LanguageCopy, + unsubscribeUrl: string +): string { + const parts: string[] = [] + + for (const block of blocks) { + switch (block.type) { + case 'heading': + parts.push(block.text) + break + case 'paragraph': + parts.push(block.text) + break + case 'list': + parts.push(block.items.map((item) => `- ${item}`).join('\n')) + break + case 'links': + parts.push(block.items.map((item) => `[${item.label}](${item.url})`).join('\t')) + break + } + } + + parts.push(copy.unsubscribeLine(unsubscribeUrl)) + parts.push(copy.addressLine) + + return parts.join('\n\n') +} diff --git a/src/lib/server/onboardingEmail/types.ts b/src/lib/server/onboardingEmail/types.ts new file mode 100644 index 000000000..aa2cc62cf --- /dev/null +++ b/src/lib/server/onboardingEmail/types.ts @@ -0,0 +1,50 @@ +import type { Intent } from '$lib/components/onboarding/options.js' + +export type { Intent } + +/** Languages this renderer hand-maintains copy for. See docs/L10N.md for why + * the site's Paraglide/inlang pipeline isn't used here (build-time only). */ +export type OnboardingEmailLanguage = 'en' | 'es' | 'fr' + +/** + * Input to the render function. Field names mirror the Airtable automation's + * `input.config()` fields (minus `to_email`/`reply_to_email`/`onboarding_email`, + * which the render endpoint doesn't need since it never sends anything itself). + */ +export type OnboardingEmailParams = { + firstName: string + /** Free-text country name, as stored on the Members record. May be empty/undefined. */ + country?: string + /** Airtable `Intent` single-select value, e.g. "Volunteer". Unknown/empty falls back to non-volunteer framing. */ + intent?: string + /** Airtable `Languages` multipleSelects value. */ + languages?: string[] | string + /** Used to build the verification and unsubscribe links. */ + airtable_id: string +} + +export type RenderedOnboardingEmail = { + subject: string + html: string + text: string +} + +/** The four intent buckets the email copy varies over. Anything else (empty, 'None', unrecognised) falls back to `keep-informed`, matching the old script's non-volunteer fallback. */ +export type IntentBucket = 'act-now' | 'keep-informed' | 'volunteer' | 'lead' + +/** A single link rendered in the chapter's link/social row. */ +export type ChapterLink = { + label: string + url: string +} + +export type ChapterBlockData = { + /** True when this is the hardcoded global fallback (no National Groups match). */ + isGlobalFallback: boolean + /** Chapter/country display name, e.g. "France", or "Global" for the fallback. */ + name: string + /** Leader name(s), joined with " and ". */ + leader: string + /** Non-empty social/community links for this chapter, in a fixed display order. */ + links: ChapterLink[] +} diff --git a/src/routes/api/onboarding-email/+server.ts b/src/routes/api/onboarding-email/+server.ts new file mode 100644 index 000000000..4ec8fa88f --- /dev/null +++ b/src/routes/api/onboarding-email/+server.ts @@ -0,0 +1,69 @@ +export const prerender = false + +import { ONBOARDING_EMAIL_RENDER_SECRET } from '$env/static/private' +import { renderOnboardingEmail } from '$lib/server/onboardingEmail/index.js' +import { json } from '@sveltejs/kit' +import { StatusCodes } from 'http-status-codes' +import type { RequestHandler } from './$types' + +// Render-only endpoint: returns {subject, html, text} for the Airtable onboarding +// automation to send itself via MailerSend's raw /v1/email call. This endpoint never +// sends email and never touches the MailerSend API key — see +// airtable-mailersend-emails.js (repo root) for the caller, which falls back to its +// existing template_id-based send if this endpoint is unreachable or returns +// something unusable. + +type RequestBody = { + first_name?: unknown + country?: unknown + intent?: unknown + languages?: unknown + airtable_id?: unknown +} + +function isNonEmptyString(value: unknown): value is string { + return typeof value === 'string' && value.length > 0 +} + +export const POST: RequestHandler = async ({ request }) => { + const authHeader = request.headers.get('authorization') ?? '' + const expected = `Bearer ${ONBOARDING_EMAIL_RENDER_SECRET}` + // Guard against an unset secret matching an unset header (both `Bearer `). + if (!ONBOARDING_EMAIL_RENDER_SECRET || authHeader !== expected) { + return new Response('Unauthorized', { status: StatusCodes.UNAUTHORIZED }) + } + + let body: RequestBody + try { + body = (await request.json()) as RequestBody + } catch { + return new Response('Invalid JSON body', { status: StatusCodes.BAD_REQUEST }) + } + + if (!isNonEmptyString(body.first_name) || !isNonEmptyString(body.airtable_id)) { + return new Response('"first_name" and "airtable_id" are required', { + status: StatusCodes.BAD_REQUEST + }) + } + + const languages = + Array.isArray(body.languages) || typeof body.languages === 'string' + ? (body.languages as string[] | string) + : undefined + + try { + const rendered = await renderOnboardingEmail({ + firstName: body.first_name, + country: typeof body.country === 'string' ? body.country : undefined, + intent: typeof body.intent === 'string' ? body.intent : undefined, + languages, + airtable_id: body.airtable_id + }) + return json(rendered) + } catch (error) { + console.error('Failed to render onboarding email:', error) + return new Response('Failed to render onboarding email', { + status: StatusCodes.INTERNAL_SERVER_ERROR + }) + } +} diff --git a/src/routes/api/unsubscribe/+server.ts b/src/routes/api/unsubscribe/+server.ts new file mode 100644 index 000000000..5e8ee598e --- /dev/null +++ b/src/routes/api/unsubscribe/+server.ts @@ -0,0 +1,66 @@ +export const prerender = false + +import { AIRTABLE_WRITE_API_KEY, MAILERSEND_API_KEY } from '$env/static/private' +import { verificationParameter } from '$lib/config.js' +import Airtable from 'airtable' +import { StatusCodes } from 'http-status-codes' +import type { RequestHandler } from './$types' + +const MEMBERS_BASE_ID = 'appWPTGqZmUcs3NWu' +const MEMBERS_TABLE_ID = 'tblL1icZBhTV1gQ9o' +const KEY_FIELD_NAME = 'Airtable ID' +const SUBSCRIBED_FIELD_NAME = 'Email subscription' +const EMAIL_FIELD_NAME = 'Email' + +// Suppressing on both domains covers the recipient regardless of which +// sender (pauseai.info or pauseai.uk) actually sent them the email. +const MAILERSEND_DOMAIN_IDS = [ + 'r83ql3p2qkzgzw1j', // pauseai.info + 'q3enl6xxy17l2vwr' // pauseai.uk +] + +export const GET: RequestHandler = async ({ url }) => { + const key = url.searchParams.get(verificationParameter) + if (!key) { + return new Response(`Parameter "${verificationParameter}" is required`, { + status: StatusCodes.BAD_REQUEST + }) + } + if (!/^[a-zA-Z0-9]+$/.test(key)) { + return new Response(`Parameter "${verificationParameter}" must be alphanumeric`, { + status: StatusCodes.BAD_REQUEST + }) + } + + const table = new Airtable({ apiKey: AIRTABLE_WRITE_API_KEY }) + .base(MEMBERS_BASE_ID) + .table(MEMBERS_TABLE_ID) + + const records = await table + .select({ + filterByFormula: `{${KEY_FIELD_NAME}} = "${key}"` + }) + .firstPage() + if (!records.length) return new Response('Record not found', { status: StatusCodes.NOT_FOUND }) + + const record = records[0] + await record.patchUpdate(Object.fromEntries([[SUBSCRIBED_FIELD_NAME, false]])) + + const email = record.get(EMAIL_FIELD_NAME) + if (email) { + await Promise.all( + MAILERSEND_DOMAIN_IDS.map((domain_id) => + fetch('https://api.mailersend.com/v1/suppressions/unsubscribes', { + method: 'POST', + headers: { + Authorization: `Bearer ${MAILERSEND_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ domain_id, recipients: [email] }) + }) + ) + ) + } + + return new Response('OK', { status: StatusCodes.OK }) +} diff --git a/src/routes/onboarding-email-preview/+page.server.ts b/src/routes/onboarding-email-preview/+page.server.ts new file mode 100644 index 000000000..f95995797 --- /dev/null +++ b/src/routes/onboarding-email-preview/+page.server.ts @@ -0,0 +1,92 @@ +export const prerender = false + +import { dev } from '$app/environment' +import { renderOnboardingEmail } from '$lib/server/onboardingEmail/index.js' +import { error } from '@sveltejs/kit' +import type { PageServerLoad } from './$types' + +// Internal QA tool for eyeballing the onboarding-email render output across a useful +// sample of intent x chapter x language combos before this ships. Not linked from +// anywhere in the site nav, and 404s outside dev so it can't be stumbled onto in +// production. (Chapter data is public National Groups info, not PII, so this is +// safe to leave dev-only rather than adding real auth.) +const SAMPLE_PARAMS: { + label: string + country: string + intent: string + languages: string[] +}[] = [ + { label: 'UK · Volunteer · en', country: 'United Kingdom', intent: 'Volunteer', languages: [] }, + { + label: 'UK · Keep informed · en', + country: 'United Kingdom', + intent: 'Keep informed', + languages: [] + }, + { + label: 'France · Lead · fr (auto by country)', + country: 'France', + intent: 'Lead', + languages: [] + }, + { + label: 'Spain · Act now · es (auto by country)', + country: 'Spain', + intent: 'Act now', + languages: [] + }, + { + label: 'Canada · Volunteer · en (no French selected)', + country: 'Canada', + intent: 'Volunteer', + languages: [] + }, + { + label: 'Canada · Volunteer · fr (French selected)', + country: 'Canada', + intent: 'Volunteer', + languages: ['French'] + }, + { + label: 'Australia · Volunteer · en (chapter with several links)', + country: 'Australia', + intent: 'Volunteer', + languages: [] + }, + { + label: 'Atlantis · Keep informed · en (no chapter match -> global fallback)', + country: 'Atlantis', + intent: 'Keep informed', + languages: [] + }, + { + label: 'Mexico · Volunteer · es (Spanish country, no chapter match)', + country: 'Mexico', + intent: 'Volunteer', + languages: [] + }, + { + label: 'Germany · empty intent · en (falls back to keep-informed framing)', + country: 'Germany', + intent: '', + languages: [] + } +] + +export const load: PageServerLoad = async () => { + if (!dev) error(404, 'Not found') + + const results = [] + for (const sample of SAMPLE_PARAMS) { + const rendered = await renderOnboardingEmail({ + firstName: 'Alex', + country: sample.country, + intent: sample.intent, + languages: sample.languages, + airtable_id: 'previewRecordId123' + }) + results.push({ label: sample.label, ...rendered }) + } + + return { results } +} diff --git a/src/routes/onboarding-email-preview/+page.svelte b/src/routes/onboarding-email-preview/+page.svelte new file mode 100644 index 000000000..6a0f1e733 --- /dev/null +++ b/src/routes/onboarding-email-preview/+page.svelte @@ -0,0 +1,63 @@ + + + + Onboarding email preview (dev only) + + +
              +

              Onboarding email preview

              +

              + Dev-only QA tool for src/lib/server/onboardingEmail. Not linked from the site, 404s + outside pnpm dev. +

              + +
              + {#each data.results as result, i} + + {/each} +
              + + {#if selected} +
              + Subject: + {selected.subject} +
              + +
              + +
              + + {#if showText} +
              {selected.text}
              + {:else} + + {/if} + {/if} +
              From 8b4c2c877a7d2cbc493d10b1f4a92a5980479cf1 Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:02:51 +0100 Subject: [PATCH 2/6] Add MailerSend and onboarding-email render secrets to template.env svelte-check generates $env/static/private types from .env, which CI populates via `cp template.env .env`. The static imports added in this branch (MAILERSEND_API_KEY in the unsubscribe route, ONBOARDING_EMAIL_RENDER_SECRET in the onboarding-email route) referenced members missing from template.env, failing `pnpm check` and blocking the deploy preview. Values left empty; Netlify secret scanning rejects real values in committed files. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019P2Yg2vt6iV6w1rAL1wS4h --- template.env | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/template.env b/template.env index 2291d4d8e..e0e349669 100644 --- a/template.env +++ b/template.env @@ -21,6 +21,17 @@ AIRTABLE_WRITE_API_KEY = "" PUBLIC_TURNSTILE_SITE_KEY = "" TURNSTILE_SECRET_KEY = "" +# MailerSend transactional email API key. +# Used by the /contact-us forms, the unsubscribe endpoint, and the onboarding-email +# render/send flow. Optional locally unless you are exercising those send paths. +# Do not paste the value here — this file is committed and Netlify secret scanning +# fails the build if a configured secret's value appears in repo files. +MAILERSEND_API_KEY = "" + +# Shared bearer secret guarding POST /api/onboarding-email so only trusted callers +# (the Airtable automation) can trigger a render/send. Optional locally. +ONBOARDING_EMAIL_RENDER_SECRET = "" + # Used for the /chat feature - optional for most local development OPENAI_KEY = "" # Used for the /write feature - optional for most local development From e945f3c28e1f3bccb02947cd54b654a3926773a6 Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:10:00 +0100 Subject: [PATCH 3/6] Fix deploy-preview build: dynamic env for onboarding-email secret Two failures blocking the deploy preview for #1065: - Netlify's build does not populate .env from template.env; it exposes only the vars configured in the Netlify environment. That list has MAILERSEND_API_KEY but not ONBOARDING_EMAIL_RENDER_SECRET, and $env/static/private fails the build outright on a missing member. Switch the onboarding-email route to $env/dynamic/private (runtime lookup, no build-time export requirement), matching the pattern in contact-us/+page.server.ts. - eslint's typed linting ran on airtable-mailersend-emails.js, which is an Airtable script-runtime file not covered by tsconfig.check.json, producing a parser error. Add it to eslint's globalIgnores. pnpm check and pnpm build both pass locally. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019P2Yg2vt6iV6w1rAL1wS4h --- eslint.config.js | 6 +++++- src/routes/api/onboarding-email/+server.ts | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 2487af2ed..ccf01d217 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -167,6 +167,10 @@ export default defineConfig( globalIgnores([ // TODO remove when done 'src/routes/api/write', - 'src/routes/write' + 'src/routes/write', + // Airtable automation script: runs in Airtable's script runtime (input.config(), + // input.secret() globals), not in this repo's build. Kept here as source of truth + // but not covered by tsconfig.check.json, so typed linting can't parse it. + 'airtable-mailersend-emails.js' ]) ) diff --git a/src/routes/api/onboarding-email/+server.ts b/src/routes/api/onboarding-email/+server.ts index 4ec8fa88f..20b7a0930 100644 --- a/src/routes/api/onboarding-email/+server.ts +++ b/src/routes/api/onboarding-email/+server.ts @@ -1,6 +1,6 @@ export const prerender = false -import { ONBOARDING_EMAIL_RENDER_SECRET } from '$env/static/private' +import { env } from '$env/dynamic/private' import { renderOnboardingEmail } from '$lib/server/onboardingEmail/index.js' import { json } from '@sveltejs/kit' import { StatusCodes } from 'http-status-codes' @@ -26,10 +26,14 @@ function isNonEmptyString(value: unknown): value is string { } export const POST: RequestHandler = async ({ request }) => { + // Dynamic (not static) private env: the secret isn't set in every deploy + // environment (e.g. deploy previews), and $env/static/private fails the build + // when an imported member is missing rather than resolving it at runtime. + const secret = env.ONBOARDING_EMAIL_RENDER_SECRET const authHeader = request.headers.get('authorization') ?? '' - const expected = `Bearer ${ONBOARDING_EMAIL_RENDER_SECRET}` + const expected = `Bearer ${secret}` // Guard against an unset secret matching an unset header (both `Bearer `). - if (!ONBOARDING_EMAIL_RENDER_SECRET || authHeader !== expected) { + if (!secret || authHeader !== expected) { return new Response('Unauthorized', { status: StatusCodes.UNAUTHORIZED }) } From a7c8e78bd38132a743f4a3150cbd1090e4f57935 Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:15:05 +0100 Subject: [PATCH 4/6] Allow onboarding-email-preview on Netlify deploy previews Gate the QA route on hostname rather than dev only: dev, localhost, and *.netlify.app can load it; the production domain still 404s. Lets reviewers eyeball the rendered emails on the PR's deploy preview without running the site locally. The page renders public National Groups data and a fake recipient, no PII, so preview URLs are an acceptable exposure. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019P2Yg2vt6iV6w1rAL1wS4h --- .../onboarding-email-preview/+page.server.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/routes/onboarding-email-preview/+page.server.ts b/src/routes/onboarding-email-preview/+page.server.ts index f95995797..f2d8dbf3a 100644 --- a/src/routes/onboarding-email-preview/+page.server.ts +++ b/src/routes/onboarding-email-preview/+page.server.ts @@ -7,9 +7,13 @@ import type { PageServerLoad } from './$types' // Internal QA tool for eyeballing the onboarding-email render output across a useful // sample of intent x chapter x language combos before this ships. Not linked from -// anywhere in the site nav, and 404s outside dev so it can't be stumbled onto in -// production. (Chapter data is public National Groups info, not PII, so this is -// safe to leave dev-only rather than adding real auth.) +// anywhere in the site nav. Available in dev and on Netlify deploy previews +// (*.netlify.app), 404s on the production domain so it can't be stumbled onto there. +// (Chapter data is public National Groups info, not PII, so this is safe to expose +// on preview URLs rather than adding real auth.) +function isAllowedHost(hostname: string): boolean { + return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.netlify.app') +} const SAMPLE_PARAMS: { label: string country: string @@ -73,8 +77,8 @@ const SAMPLE_PARAMS: { } ] -export const load: PageServerLoad = async () => { - if (!dev) error(404, 'Not found') +export const load: PageServerLoad = async ({ url }) => { + if (!dev && !isAllowedHost(url.hostname)) error(404, 'Not found') const results = [] for (const sample of SAMPLE_PARAMS) { From e83f33b92623471061e9c49f0450283ae88e6811 Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:42:52 +0100 Subject: [PATCH 5/6] Rebuild onboarding-email-preview around individual inputs Replace the fixed list of 10 preset intent x chapter x language combos with a form exposing each renderer input on its own: first name (text), country (free-text with a datalist of the 196 known names), intent (select, including the empty/unrecognised case), and languages (checkboxes over the stored vocabulary). Values live in the query string so a preview is shareable, and any change auto-submits the GET form (plain submit button as the no-JS fallback). Adds a "resolved" panel showing which branch the inputs hit: email language, intent bucket, and whether the country matched a National Groups chapter (with leader and link count) or fell back to Global. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019P2Yg2vt6iV6w1rAL1wS4h --- .../onboarding-email-preview/+page.server.ts | 133 +++++++-------- .../onboarding-email-preview/+page.svelte | 151 ++++++++++++++---- 2 files changed, 170 insertions(+), 114 deletions(-) diff --git a/src/routes/onboarding-email-preview/+page.server.ts b/src/routes/onboarding-email-preview/+page.server.ts index f2d8dbf3a..746ff200f 100644 --- a/src/routes/onboarding-email-preview/+page.server.ts +++ b/src/routes/onboarding-email-preview/+page.server.ts @@ -1,96 +1,71 @@ export const prerender = false import { dev } from '$app/environment' +import { COUNTRIES, INTENTS, LANGUAGES } from '$lib/components/onboarding/options.js' +import { getChapterForOnboardingEmail } from '$lib/server/onboardingEmail/chapter.js' +import { resolveIntentBucket } from '$lib/server/onboardingEmail/blocks.js' import { renderOnboardingEmail } from '$lib/server/onboardingEmail/index.js' +import { resolveOnboardingEmailLanguage } from '$lib/server/onboardingEmail/language.js' import { error } from '@sveltejs/kit' import type { PageServerLoad } from './$types' -// Internal QA tool for eyeballing the onboarding-email render output across a useful -// sample of intent x chapter x language combos before this ships. Not linked from -// anywhere in the site nav. Available in dev and on Netlify deploy previews -// (*.netlify.app), 404s on the production domain so it can't be stumbled onto there. -// (Chapter data is public National Groups info, not PII, so this is safe to expose -// on preview URLs rather than adding real auth.) +// Internal QA tool for eyeballing the onboarding-email render output. Every input the +// renderer takes (first name, country, intent, languages) is an individual control on +// the page; the query string holds the current values so a given preview is +// shareable/bookmarkable. Not linked from the site nav. Available in dev and on +// Netlify deploy previews (*.netlify.app), 404s on the production domain so it can't +// be stumbled onto there. (Chapter data is public National Groups info, not PII, so +// this is safe to expose on preview URLs rather than adding real auth.) function isAllowedHost(hostname: string): boolean { return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.netlify.app') } -const SAMPLE_PARAMS: { - label: string - country: string - intent: string - languages: string[] -}[] = [ - { label: 'UK · Volunteer · en', country: 'United Kingdom', intent: 'Volunteer', languages: [] }, - { - label: 'UK · Keep informed · en', - country: 'United Kingdom', - intent: 'Keep informed', - languages: [] - }, - { - label: 'France · Lead · fr (auto by country)', - country: 'France', - intent: 'Lead', - languages: [] - }, - { - label: 'Spain · Act now · es (auto by country)', - country: 'Spain', - intent: 'Act now', - languages: [] - }, - { - label: 'Canada · Volunteer · en (no French selected)', - country: 'Canada', - intent: 'Volunteer', - languages: [] - }, - { - label: 'Canada · Volunteer · fr (French selected)', - country: 'Canada', - intent: 'Volunteer', - languages: ['French'] - }, - { - label: 'Australia · Volunteer · en (chapter with several links)', - country: 'Australia', - intent: 'Volunteer', - languages: [] - }, - { - label: 'Atlantis · Keep informed · en (no chapter match -> global fallback)', - country: 'Atlantis', - intent: 'Keep informed', - languages: [] - }, - { - label: 'Mexico · Volunteer · es (Spanish country, no chapter match)', - country: 'Mexico', - intent: 'Volunteer', - languages: [] - }, - { - label: 'Germany · empty intent · en (falls back to keep-informed framing)', - country: 'Germany', - intent: '', - languages: [] - } -] + +const DEFAULTS = { + firstName: 'Alex', + country: 'United Kingdom', + intent: 'Volunteer', + languages: [] as string[] +} export const load: PageServerLoad = async ({ url }) => { if (!dev && !isAllowedHost(url.hostname)) error(404, 'Not found') - const results = [] - for (const sample of SAMPLE_PARAMS) { - const rendered = await renderOnboardingEmail({ - firstName: 'Alex', - country: sample.country, - intent: sample.intent, - languages: sample.languages, - airtable_id: 'previewRecordId123' - }) - results.push({ label: sample.label, ...rendered }) + const params = url.searchParams + const hasQuery = [...params.keys()].length > 0 + + const firstName = params.get('firstName') ?? DEFAULTS.firstName + const country = hasQuery ? (params.get('country') ?? '') : DEFAULTS.country + const intent = hasQuery ? (params.get('intent') ?? '') : DEFAULTS.intent + const languages = hasQuery ? params.getAll('languages') : DEFAULTS.languages + + const rendered = await renderOnboardingEmail({ + firstName, + country, + intent, + languages, + airtable_id: 'previewRecordId123' + }) + + // Surfaced in a small "what the inputs resolved to" panel so it's obvious which + // branch of the matrix produced the email on screen. + const chapter = await getChapterForOnboardingEmail(country) + const resolved = { + language: resolveOnboardingEmailLanguage(country, languages), + intentBucket: resolveIntentBucket(intent), + chapterName: chapter.name, + chapterLeader: chapter.leader, + chapterIsGlobalFallback: chapter.isGlobalFallback, + chapterLinkCount: chapter.links.length } - return { results } + return { + form: { firstName, country, intent, languages }, + options: { + countries: COUNTRIES, + intents: INTENTS, + languages: LANGUAGES + }, + resolved, + rendered + } } diff --git a/src/routes/onboarding-email-preview/+page.svelte b/src/routes/onboarding-email-preview/+page.svelte index 6a0f1e733..e596f40ab 100644 --- a/src/routes/onboarding-email-preview/+page.svelte +++ b/src/routes/onboarding-email-preview/+page.svelte @@ -3,10 +3,16 @@ let { data }: { data: PageData } = $props() - let selectedIndex = $state(0) let showText = $state(false) - let selected = $derived(data.results[selectedIndex]) + // Auto-submit the GET form on any control change so the preview updates without a + // manual button press. Falls back to the visible submit button when JS is off. + let formEl: HTMLFormElement | undefined = $state() + function submitNow() { + formEl?.requestSubmit() + } + + let selectedLanguages = $derived(new Set(data.form.languages)) @@ -17,47 +23,122 @@

              Onboarding email preview

              Dev-only QA tool for src/lib/server/onboardingEmail. Not linked from the site, 404s - outside pnpm dev. + on the production domain. Adjust any input to re-render.

              -
              - {#each data.results as result, i} - - {/each} -
              +
              + + - {#if selected} -
              - Subject: - {selected.subject} + +
              + + + {#each data.options.countries as c} + + {/each} + + + Free text — matches a National Groups record by exact (case-insensitive) name. +
              -
              - + + + + Languages +
              + {#each data.options.languages as l} + + {/each}
              - {#if showText} -
              {selected.text}
              + + + + +
              + Resolved: + language {data.resolved.language} + · intent bucket {data.resolved.intentBucket} + · chapter + {#if data.resolved.chapterIsGlobalFallback} + Global fallback (no National Groups match) {:else} - + {data.resolved.chapterName} — leader {data.resolved.chapterLeader}, + {data.resolved.chapterLinkCount} link{data.resolved.chapterLinkCount === 1 ? '' : 's'} {/if} +
              + +
              + Subject: + {data.rendered.subject} +
              + +
              + +
              + + {#if showText} +
              {data
              +				.rendered.text}
              + {:else} + {/if}
              From 483a8d4598f114f2b0ea97e109467e70464574db Mon Sep 17 00:00:00 2001 From: Harry Turnbull <52940497+hturnbull93@users.noreply.github.com> Date: Wed, 2 Sep 2026 19:53:41 +0100 Subject: [PATCH 6/6] Preview: explicit language radio, chapter-only country select, light scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add an optional languageOverride to OnboardingEmailParams so the preview can force en/es/fr directly instead of reverse-engineering country + languages. The production render endpoint never sets it. - Preview language control is now three radios (en/es/fr) — the only languages copy.ts actually has copy for. - Country is a - - {#each data.options.countries as c} - - {/each} - - - Free text — matches a National Groups record by exact (case-insensitive) name. - + Language +
              + {#each data.options.languages as lang} + + {/each}
              @@ -75,21 +79,24 @@ {/each} - Languages -
              - {#each data.options.languages as l} - - {/each} + +
              + + + Active National Groups records ({data.options.countries.length}). Sets the chapter block + only; language is chosen above. Any other country takes the global fallback. +
              @@ -102,11 +109,10 @@
              Resolved: - language {data.resolved.language} - · intent bucket {data.resolved.intentBucket} + intent bucket {data.resolved.intentBucket} · chapter {#if data.resolved.chapterIsGlobalFallback} Global fallback (no National Groups match) @@ -132,7 +138,7 @@ {#if showText}
              {data
              +			style="white-space: pre-wrap; background: #f6f6f6; color: #222; padding: 16px; border-radius: 6px; font-size: 13px; line-height: 1.5;">{data
               				.rendered.text}
              {:else}