copywriting
Marketing copy for homepages, landing pages, pricing pages, and other conversion-focused web pages.
posthog-data-handling
PostHog PII handling, GDPR compliance, consent management, data deletion,
Full skill instructions
Privacy-safe analytics with PostHog. Covers property sanitization to strip PII before events leave the browser, consent-based tracking (opt-in/opt-out), GDPR data subject access requests and deletion, and PostHog's built-in privacy controls (IP masking, session recording masking).
posthog-js and/or posthog-node installedimport posthog from 'posthog-js';
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: 'https://us.i.posthog.com',
// Disable autocapture to control exactly what's captured
autocapture: false,
// Respect browser Do Not Track setting
respect_dnt: true,
// Don't capture until user consents
opt_out_capturing_by_default: false, // Set true for opt-in model
// Sanitize ALL properties before they leave the browser
sanitize_properties: (properties, eventName) => {
// Remove IP address
delete properties['$ip'];
// Remove potentially identifying properties
delete properties['$device_id'];
// Redact URLs containing tokens or auth info
if (properties['$current_url']) {
properties['$current_url'] = properties['$current_url']
.replace(/token=[^&]+/g, 'token=[REDACTED]')
.replace(/key=[^&]+/g, 'key=[REDACTED]')
.replace(/session=[^&]+/g, 'session=[REDACTED]');
}
// Redact referrer tokens
if (properties['$referrer']) {
properties['$referrer'] = properties['$referrer']
.replace(/token=[^&]+/g, 'token=[REDACTED]');
}
return properties;
},
// Session recording privacy
session_recording: {
maskAllInputs: true, // Mask all input fields
maskTextSelector: '.pii-data', // Mask specific elements
},
});
// Cookie consent integration
interface ConsentState {
analytics: boolean;
functional: boolean;
marketing: boolean;
}
export function handleConsentChange(consent: ConsentState) {
if (consent.analytics) {
// User opted in — start capturing
posthog.opt_in_capturing();
} else {
// User opted out — stop capturing and clear local data
posthog.opt_out_capturing();
posthog.reset(); // Clears distinct_id, device_id, session data
}
}
// Check consent before identifying (PII)
export function identifyWithConsent(
userId: string,
properties: Record<string, any>,
hasAnalyticsConsent: boolean
) {
if (!hasAnalyticsConsent) return;
// Only send non-PII properties by default
const safeProperties: Record<string, any> = {
plan: properties.plan,
signup_date: properties.signupDate,
account_type: properties.accountType,
// Do NOT include: email, name, phone, address
};
posthog.identify(userId, safeProperties);
}
// On page load: restore consent state
export function restoreConsent() {
const consent = getCookieConsent(); // Your consent mechanism
if (consent?.analytics === false) {
posthog.opt_out_capturing();
}
}
// Find a person by email and export their data
async function handleSubjectAccessRequest(email: string) {
const personalKey = process.env.POSTHOG_PERSONAL_API_KEY!;
const projectId = process.env.POSTHOG_PROJECT_ID!;
// 1. Find the person by email property
const searchResponse = await fetch(
`https://app.posthog.com/api/projects/${projectId}/persons/?properties=[{"key":"email","value":"${encodeURIComponent(email)}","type":"person"}]`,
{ headers: { Authorization: `Bearer ${personalKey}` } }
);
const searchData = await searchResponse.json();
if (!searchData.results?.length) {
return { found: false, message: 'No person found with that email' };
}
const person = searchData.results[0];
const distinctId = person.distinct_ids[0];
// 2. Export their events (strip PII from export)
const eventsResponse = await fetch(
`https://app.posthog.com/api/projects/${projectId}/query/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${personalKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: {
kind: 'HogQLQuery',
query: `SELECT event, timestamp, properties FROM events WHERE distinct_id = '${distinctId}' ORDER BY timestamp DESC LIMIT 1000`,
},
}),
}
);
const eventsData = await eventsResponse.json();
return {
found: true,
person: {
distinct_ids: person.distinct_ids,
properties: person.properties,
created_at: person.created_at,
},
events_count: eventsData.results?.length || 0,
events: eventsData.results,
};
}
// Delete a person and all their events
async function handleDeletionRequest(email: string) {
const personalKey = process.env.POSTHOG_PERSONAL_API_KEY!;
const projectId = process.env.POSTHOG_PROJECT_ID!;
// 1. Find the person
const searchResponse = await fetch(
`https://app.posthog.com/api/projects/${projectId}/persons/?properties=[{"key":"email","value":"${encodeURIComponent(email)}","type":"person"}]`,
{ headers: { Authorization: `Bearer ${personalKey}` } }
);
const searchData = await searchResponse.json();
if (!searchData.results?.length) {
return { deleted: false, reason: 'Person not found' };
}
const personId = searchData.results[0].id;
// 2. Delete the person (PostHog also deletes associated events)
const deleteResponse = await fetch(
`https://app.posthog.com/api/projects/${projectId}/persons/${personId}/`,
{
method: 'DELETE',
headers: { Authorization: `Bearer ${personalKey}` },
}
);
if (!deleteResponse.ok) {
throw new Error(`Deletion failed: ${deleteResponse.status}`);
}
return {
deleted: true,
personId,
timestamp: new Date().toISOString(),
};
}
// Strip PII from HogQL query results before exporting
const BLOCKED_PROPERTIES = ['$ip', 'email', 'phone', 'name', 'address', 'ssn'];
async function safeExport(hogql: string) {
const response = await fetch(
`https://app.posthog.com/api/projects/${process.env.POSTHOG_PROJECT_ID}/query/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.POSTHOG_PERSONAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query: { kind: 'HogQLQuery', query: hogql } }),
}
);
const data = await response.json();
// Remove blocked columns from results
if (data.columns && data.results) {
const blockedIndexes = new Set(
data.columns.map((col: string, i: number) =>
BLOCKED_PROPERTIES.some(b => col.toLowerCase().includes(b)) ? i : -1
).filter((i: number) => i >= 0)
);
data.columns = data.columns.filter((_: string, i: number) => !blockedIndexes.has(i));
data.results = data.results.map((row: any[]) =>
row.filter((_: any, i: number) => !blockedIndexes.has(i))
);
}
return data;
}
| Issue | Cause | Solution |
|---|---|---|
| PII in autocapture events | Form data captured automatically | Disable autocapture, use manual capture |
| IP address in events | Not stripped by sanitize_properties | Add delete properties['$ip'] |
| Consent not persisted | opt_out state lost on reload | Store consent in cookie, call opt_out on load |
| Deletion API returns 404 | Wrong person ID or already deleted | Search by email first, check response |
| Session recordings show PII | Text not masked | Add maskAllInputs: true and maskTextSelector |
sanitize_properties strips PII before events leave browseropt_in_capturing / opt_out_capturingrespect_dnt: true in PostHog initMarketing copy for homepages, landing pages, pricing pages, and other conversion-focused web pages.
Fast image generation with Flux 2 Klein, optimized for sub-second iteration and multi-reference brand styling.
Psychological principles and mental models for understanding customer behavior and influencing marketing decisions.
Plan searchable and shareable content that drives traffic, builds authority, and generates leads.
Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.
Brainstorm and prioritize marketing strategies tailored to your SaaS stage, budget, and goals.
Optimize content for discovery, citation, and visibility across AI search platforms and LLM assistants.
Systematic improvement of existing marketing copy through seven focused editing passes.
Plan and optimize your website's page hierarchy, navigation, URL structure, and internal linking.
Design and optimize systems connecting marketing, sales, and customer success into a unified revenue engine.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Convert websites into professional HyperFrames videos with guided storyboarding and brand-aligned composition.
Revolutionize Video Creation with AI-Generated Avatars and Voices
Social media on autopilot
Design custom logos instantly with AI.
Text-heavy marketing and design images
Boost Your SEO with Content and SEO Silos
Revolutionize Your Marketing with AI-Powered MarketingBlocks!
10X faster & free AI content creation for 100+ use cases
Tribescaler | Make Your Tweets Go Viral