How SpaceZero integrates with ConvertKit for newsletter management including double opt-in confirmation and unsubscribe handling.
SpaceZero provides a complete newsletter system via ConvertKit.
User → Frontend Form → Edge Function → ConvertKit API
ConvertKit handles confirmation emails automatically:
/subscribeNo additional code needed!
// components/ui/newsletter.tsx
const NewsletterForm = () => {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
const response = await fetch('/api/newsletter', {
method: 'POST',
body: JSON.stringify({ email }),
});
};
return (
<form onSubmit={handleSubmit}>
<Input type="email" value={email} onChange={...} />
<Button type="submit" disabled={loading}>
{loading ? 'Subscribing...' : 'Subscribe'}
</Button>
</form>
);
};
// functions/api/newsletter.ts
const response = await fetch(
`https://api.convertkit.com/v3/accounts/${accountId}/forms/${formId}/subscribe`,
{
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}` },
body: JSON.stringify({ api_key: apiKey, email }),
}
);
// GET /api/newsletter/[email protected]
export const onRequestGet: PagesFunction<Env> = async ({ request, env }) => {
// 1. Find subscriber ID by email
// 2. Call ConvertKit /unsubscribe
// 3. Return success page
};
Add to ConvertKit email templates:
<a href="https://your-app.space/api/newsletter/unsubscribe?email={{ subscriber.email }}">
Unsubscribe
</a>
Newsletter toggle in Profile → Preferences:
<Toggle
checked={newsletterSubscribed}
onChange={handleNewsletterToggle}
disabled={isGuest || !appConfig.newsletter.enabled}
/>
Syncs to user_profiles.newsletter_subscribed.
All app configuration is environment-defined — src/app.ts only reads and normalizes .env:
// src/app.ts
newsletter: {
enabled: import.meta.env.VITE_NEWSLETTER_ENABLED === 'true',
convertKitFormId: import.meta.env.VITE_CONVERTKIT_FORM_ID || undefined,
description: import.meta.env.VITE_NEWSLETTER_DESCRIPTION || 'Stay updated with new features and releases.',
},
Note: VITE_* variables end up in the browser bundle. Only the form ID is safe to expose — the edge function reads the API key and account ID from server-side secrets, so they never appear in client config.
CONVERTKIT_API_KEY=xxx
CONVERTKIT_ACCOUNT_ID=xxx
CONVERTKIT_FORM_ID=xxx
CONVERTKIT_FORM_ID is read server-side from Cloudflare Pages secrets (same value as the client-side VITE_CONVERTKIT_FORM_ID).