From 2ddfceca3606b0d4d832f83fce14c7277b5eaa35 Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Sun, 28 Dec 2025 10:46:46 +0100 Subject: [PATCH] docs: Improve docs with core-concept and guides --- apps/wiki/app/[[...slug]]/page.tsx | 10 + apps/wiki/app/llms.mdx/[[...slug]]/route.ts | 24 ++ apps/wiki/cli.json | 13 + apps/wiki/components/page-actions.tsx | 247 ++++++++++++++++++ apps/wiki/components/ui/button.tsx | 28 ++ .../conditional-branching.mdx | 46 ---- .../docs/automation-patterns/meta.json | 4 - .../visual-builder-guide.mdx | 37 --- .../automation-patterns/workflow-patterns.mdx | 49 ---- .../content/docs/concepts/attachments.mdx | 46 ---- apps/wiki/content/docs/concepts/billing.mdx | 18 ++ .../docs/concepts/campaigns-vs-workflows.mdx | 47 ---- apps/wiki/content/docs/concepts/campaigns.mdx | 15 ++ .../docs/concepts/contacts-and-data.mdx | 107 -------- apps/wiki/content/docs/concepts/contacts.mdx | 46 ++++ .../content/docs/concepts/custom-domains.mdx | 43 --- .../docs/concepts/events-and-triggers.mdx | 53 ---- apps/wiki/content/docs/concepts/meta.json | 13 +- .../docs/concepts/segments-and-filters.mdx | 40 --- apps/wiki/content/docs/concepts/segments.mdx | 20 ++ .../content/docs/concepts/templates-types.mdx | 36 --- apps/wiki/content/docs/concepts/templates.mdx | 24 ++ .../docs/concepts/transactional-emails.mdx | 13 + .../content/docs/concepts/troubleshooting.mdx | 44 ---- apps/wiki/content/docs/concepts/webhooks.mdx | 47 ---- apps/wiki/content/docs/concepts/workflows.mdx | 41 +++ .../docs/getting-started/authentication.mdx | 163 ------------ .../docs/getting-started/introduction.mdx | 64 ----- .../content/docs/getting-started/meta.json | 4 - .../docs/getting-started/quick-start.mdx | 129 --------- apps/wiki/content/docs/guides/api-keys.mdx | 19 ++ .../wiki/content/docs/guides/list-hygiene.mdx | 31 +++ .../wiki/content/docs/guides/localization.mdx | 15 ++ apps/wiki/content/docs/guides/meta.json | 3 + apps/wiki/content/docs/guides/tracking.mdx | 32 +++ .../content/docs/guides/verifying-domains.mdx | 17 ++ apps/wiki/content/docs/index.mdx | 34 +-- apps/wiki/content/docs/meta.json | 9 +- .../tutorials/first-transactional-email.mdx | 56 ---- apps/wiki/content/docs/tutorials/meta.json | 4 - .../docs/tutorials/newsletter-campaign.mdx | 39 --- .../tutorials/segment-based-targeting.mdx | 33 --- .../tutorials/welcome-series-workflow.mdx | 68 ----- apps/wiki/lib/cn.ts | 1 + apps/wiki/lib/get-llm-text.ts | 10 + apps/wiki/source.config.ts | 5 + 46 files changed, 652 insertions(+), 1195 deletions(-) create mode 100644 apps/wiki/app/llms.mdx/[[...slug]]/route.ts create mode 100644 apps/wiki/cli.json create mode 100644 apps/wiki/components/page-actions.tsx create mode 100644 apps/wiki/components/ui/button.tsx delete mode 100644 apps/wiki/content/docs/automation-patterns/conditional-branching.mdx delete mode 100644 apps/wiki/content/docs/automation-patterns/meta.json delete mode 100644 apps/wiki/content/docs/automation-patterns/visual-builder-guide.mdx delete mode 100644 apps/wiki/content/docs/automation-patterns/workflow-patterns.mdx delete mode 100644 apps/wiki/content/docs/concepts/attachments.mdx create mode 100644 apps/wiki/content/docs/concepts/billing.mdx delete mode 100644 apps/wiki/content/docs/concepts/campaigns-vs-workflows.mdx create mode 100644 apps/wiki/content/docs/concepts/campaigns.mdx delete mode 100644 apps/wiki/content/docs/concepts/contacts-and-data.mdx create mode 100644 apps/wiki/content/docs/concepts/contacts.mdx delete mode 100644 apps/wiki/content/docs/concepts/custom-domains.mdx delete mode 100644 apps/wiki/content/docs/concepts/events-and-triggers.mdx delete mode 100644 apps/wiki/content/docs/concepts/segments-and-filters.mdx create mode 100644 apps/wiki/content/docs/concepts/segments.mdx delete mode 100644 apps/wiki/content/docs/concepts/templates-types.mdx create mode 100644 apps/wiki/content/docs/concepts/templates.mdx create mode 100644 apps/wiki/content/docs/concepts/transactional-emails.mdx delete mode 100644 apps/wiki/content/docs/concepts/troubleshooting.mdx delete mode 100644 apps/wiki/content/docs/concepts/webhooks.mdx create mode 100644 apps/wiki/content/docs/concepts/workflows.mdx delete mode 100644 apps/wiki/content/docs/getting-started/authentication.mdx delete mode 100644 apps/wiki/content/docs/getting-started/introduction.mdx delete mode 100644 apps/wiki/content/docs/getting-started/meta.json delete mode 100644 apps/wiki/content/docs/getting-started/quick-start.mdx create mode 100644 apps/wiki/content/docs/guides/api-keys.mdx create mode 100644 apps/wiki/content/docs/guides/list-hygiene.mdx create mode 100644 apps/wiki/content/docs/guides/localization.mdx create mode 100644 apps/wiki/content/docs/guides/meta.json create mode 100644 apps/wiki/content/docs/guides/tracking.mdx create mode 100644 apps/wiki/content/docs/guides/verifying-domains.mdx delete mode 100644 apps/wiki/content/docs/tutorials/first-transactional-email.mdx delete mode 100644 apps/wiki/content/docs/tutorials/meta.json delete mode 100644 apps/wiki/content/docs/tutorials/newsletter-campaign.mdx delete mode 100644 apps/wiki/content/docs/tutorials/segment-based-targeting.mdx delete mode 100644 apps/wiki/content/docs/tutorials/welcome-series-workflow.mdx create mode 100644 apps/wiki/lib/cn.ts create mode 100644 apps/wiki/lib/get-llm-text.ts diff --git a/apps/wiki/app/[[...slug]]/page.tsx b/apps/wiki/app/[[...slug]]/page.tsx index 9433874..6de763a 100644 --- a/apps/wiki/app/[[...slug]]/page.tsx +++ b/apps/wiki/app/[[...slug]]/page.tsx @@ -4,6 +4,7 @@ import {notFound} from 'next/navigation'; import {source} from '@/lib/source'; import {getMDXComponents} from '@/mdx-components'; +import {LLMCopyButton, ViewOptions} from '@/components/page-actions'; export const dynamic = 'force-dynamic'; @@ -18,6 +19,15 @@ export default async function Page(props: {params: Promise<{slug?: string[]}>}) {page.data.title} {page.data.description} + +
+ + +
+ }, +) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + return new Response(await getLLMText(page), { + headers: { + 'Content-Type': 'text/markdown', + }, + }); +} + +export function generateStaticParams() { + return source.generateParams(); +} diff --git a/apps/wiki/cli.json b/apps/wiki/cli.json new file mode 100644 index 0000000..4849e33 --- /dev/null +++ b/apps/wiki/cli.json @@ -0,0 +1,13 @@ +{ + "$schema": "node_modules/@fumadocs/cli/dist/schema/default.json", + "aliases": { + "uiDir": "./components/ui", + "componentsDir": "./components", + "blockDir": "./components", + "cssDir": "./styles", + "libDir": "./lib" + }, + "baseDir": "", + "uiLibrary": "radix-ui", + "commands": {} +} \ No newline at end of file diff --git a/apps/wiki/components/page-actions.tsx b/apps/wiki/components/page-actions.tsx new file mode 100644 index 0000000..4c62f95 --- /dev/null +++ b/apps/wiki/components/page-actions.tsx @@ -0,0 +1,247 @@ +'use client'; +import { useMemo, useState } from 'react'; +import { + Check, + ChevronDown, + Copy, + ExternalLinkIcon, + MessageCircleIcon, +} from 'lucide-react'; +import { cn } from '../lib/cn'; +import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'; +import { buttonVariants } from './ui/button'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from 'fumadocs-ui/components/ui/popover'; +import { cva } from 'class-variance-authority'; + +const cache = new Map(); + +export function LLMCopyButton({ + /** + * A URL to fetch the raw Markdown/MDX content of page + */ + markdownUrl, +}: { + markdownUrl: string; +}) { + const [isLoading, setLoading] = useState(false); + const [checked, onClick] = useCopyButton(async () => { + const cached = cache.get(markdownUrl); + if (cached) return navigator.clipboard.writeText(cached); + + setLoading(true); + + try { + await navigator.clipboard.write([ + new ClipboardItem({ + 'text/plain': fetch(markdownUrl).then(async (res) => { + const content = await res.text(); + cache.set(markdownUrl, content); + + return content; + }), + }), + ]); + } finally { + setLoading(false); + } + }); + + return ( + + ); +} + +const optionVariants = cva( + 'text-sm p-2 rounded-lg inline-flex items-center gap-2 hover:text-fd-accent-foreground hover:bg-fd-accent [&_svg]:size-4', +); + +export function ViewOptions({ + markdownUrl, + githubUrl, +}: { + /** + * A URL to the raw Markdown/MDX content of page + */ + markdownUrl: string; + + /** + * Source file URL on GitHub + */ + githubUrl: string; +}) { + const items = useMemo(() => { + const fullMarkdownUrl = + typeof window !== 'undefined' + ? new URL(markdownUrl, window.location.origin) + : 'loading'; + const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`; + + return [ + { + title: 'Open in GitHub', + href: githubUrl, + icon: ( + + GitHub + + + ), + }, + { + title: 'Open in Scira AI', + href: `https://scira.ai/?${new URLSearchParams({ + q, + })}`, + icon: ( + + Scira AI + + + + + + + + + ), + }, + { + title: 'Open in ChatGPT', + href: `https://chatgpt.com/?${new URLSearchParams({ + hints: 'search', + q, + })}`, + icon: ( + + OpenAI + + + ), + }, + { + title: 'Open in Claude', + href: `https://claude.ai/new?${new URLSearchParams({ + q, + })}`, + icon: ( + + Anthropic + + + ), + }, + { + title: 'Open in T3 Chat', + href: `https://t3.chat/new?${new URLSearchParams({ + q, + })}`, + icon: , + }, + ]; + }, [githubUrl, markdownUrl]); + + return ( + + + Open + + + + {items.map((item) => ( + + {item.icon} + {item.title} + + + ))} + + + ); +} diff --git a/apps/wiki/components/ui/button.tsx b/apps/wiki/components/ui/button.tsx new file mode 100644 index 0000000..b427d4e --- /dev/null +++ b/apps/wiki/components/ui/button.tsx @@ -0,0 +1,28 @@ +import { cva, type VariantProps } from 'class-variance-authority'; + +const variants = { + primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80', + outline: 'border hover:bg-fd-accent hover:text-fd-accent-foreground', + ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground', + secondary: + 'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground', +} as const; + +export const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring', + { + variants: { + variant: variants, + // fumadocs use `color` instead of `variant` + color: variants, + size: { + sm: 'gap-1 px-2 py-1.5 text-xs', + icon: 'p-1.5 [&_svg]:size-5', + 'icon-sm': 'p-1.5 [&_svg]:size-4.5', + 'icon-xs': 'p-1 [&_svg]:size-4', + }, + }, + }, +); + +export type ButtonProps = VariantProps; diff --git a/apps/wiki/content/docs/automation-patterns/conditional-branching.mdx b/apps/wiki/content/docs/automation-patterns/conditional-branching.mdx deleted file mode 100644 index a92b0f2..0000000 --- a/apps/wiki/content/docs/automation-patterns/conditional-branching.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Conditional Branching -description: If/then logic in workflows -icon: GitBranch ---- - -## Overview - -Conditions split workflows into two paths based on contact data. - -``` -[Condition: plan = "premium"?] - ├─ True → [Premium email] - └─ False → [Upgrade offer] -``` - -## Operators - -| Operator | Example | -|----------|---------| -| `equals` | `plan equals "pro"` | -| `notEquals` | `plan notEquals "free"` | -| `contains` | `email contains "@company.com"` | -| `greaterThan` | `mrr greaterThan 100` | -| `lessThan` | `loginCount lessThan 5` | -| `exists` | `company exists` | -| `notExists` | `lastName notExists` | - -## Nested conditions - -Chain for multi-tier logic: - -``` -[Condition: enterprise?] - ├─ True → [Enterprise email] - └─ False → [Condition: pro?] - ├─ True → [Pro email] - └─ False → [Free email] -``` - -## Tips - -- Store numbers as numbers, not strings -- Check field exists before comparing -- Limit to 3 levels of nesting -- Test both paths diff --git a/apps/wiki/content/docs/automation-patterns/meta.json b/apps/wiki/content/docs/automation-patterns/meta.json deleted file mode 100644 index 3d8226b..0000000 --- a/apps/wiki/content/docs/automation-patterns/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Automation", - "pages": ["visual-builder-guide", "workflow-patterns", "conditional-branching"] -} diff --git a/apps/wiki/content/docs/automation-patterns/visual-builder-guide.mdx b/apps/wiki/content/docs/automation-patterns/visual-builder-guide.mdx deleted file mode 100644 index d547de2..0000000 --- a/apps/wiki/content/docs/automation-patterns/visual-builder-guide.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Visual Builder -description: Build workflows with drag-and-drop -icon: Workflow ---- - -## Canvas controls - -- **+/-** — Zoom in/out -- **Fit** — Center workflow -- **Auto-Layout** — Arrange nodes - -## Step types - -| Step | Description | -|------|-------------| -| **Trigger** | Starting point (event, segment, schedule) | -| **Send Email** | Send a template | -| **Delay** | Wait minutes, hours, or days | -| **Wait for Event** | Pause until event or timeout | -| **Condition** | Branch based on contact data | -| **Webhook** | Send HTTP request | -| **Update Contact** | Modify contact fields | -| **Exit** | End workflow | - -## Building - -1. Click step to configure -2. Connect steps by dragging -3. Conditions require both true/false paths - -## Tips - -- Keep workflows to 5-10 steps -- Use descriptive step names -- Test with a test contact before enabling -- Space emails 12-24 hours apart diff --git a/apps/wiki/content/docs/automation-patterns/workflow-patterns.mdx b/apps/wiki/content/docs/automation-patterns/workflow-patterns.mdx deleted file mode 100644 index f951e3e..0000000 --- a/apps/wiki/content/docs/automation-patterns/workflow-patterns.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Workflow Patterns -description: Common workflow templates -icon: Layers ---- - -## Linear sequence - -``` -[Trigger] → [Email] → [Delay] → [Email] → [Exit] -``` - -Use for: onboarding, drip campaigns - -## Wait and branch - -``` -[Trigger] → [Wait for Event] - ├─ Event → [Success email] → [Exit] - └─ Timeout → [Reminder] → [Exit] -``` - -Use for: trial conversion, activation - -## Conditional branch - -``` -[Trigger] → [Condition: plan = premium?] - ├─ True → [Premium email] → [Exit] - └─ False → [Upgrade offer] → [Exit] -``` - -Use for: personalization by tier - -## Multi-step recovery - -``` -[Trigger: cart_abandoned] - ↓ -[Delay: 1 hour] - ↓ -[Send: Reminder] - ↓ -[Wait for: purchase, timeout: 24h] - ├─ Purchased → [Exit] - └─ Timeout → [Send: Discount] → [Exit] -``` - -Use for: cart abandonment, re-engagement diff --git a/apps/wiki/content/docs/concepts/attachments.mdx b/apps/wiki/content/docs/concepts/attachments.mdx deleted file mode 100644 index d9f97a5..0000000 --- a/apps/wiki/content/docs/concepts/attachments.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Email Attachments -description: Send files with emails -icon: Paperclip ---- - -## Limits - -- Max 10 attachments per email -- Max 10MB total size -- Any file type supported - -## API usage - -```json -{ - "to": "user@example.com", - "subject": "Your Invoice", - "body": "

Invoice attached.

", - "attachments": [ - { - "filename": "invoice.pdf", - "content": "JVBERi0xLjQK...", - "contentType": "application/pdf" - } - ] -} -``` - -## Attachment fields - -| Field | Description | -|-------|-------------| -| `filename` | Display name (max 255 chars) | -| `content` | Base64-encoded file content | -| `contentType` | MIME type (e.g., `application/pdf`) | - -## Base64 encoding - -```javascript -import fs from 'fs'; - -const fileBuffer = fs.readFileSync('invoice.pdf'); -const base64Content = fileBuffer.toString('base64'); -``` - diff --git a/apps/wiki/content/docs/concepts/billing.mdx b/apps/wiki/content/docs/concepts/billing.mdx new file mode 100644 index 0000000..359c94c --- /dev/null +++ b/apps/wiki/content/docs/concepts/billing.mdx @@ -0,0 +1,18 @@ +--- +title: Billing +description: Understand Plunk's billing model +icon: CreditCard +--- + +Plunk's pricing is based on the number of emails sent each month. You can send both marketing and transactional emails under the same plan. + +## Free Tier +Free tier projects can send up to 1,000 emails per month at no cost. Projects on this tier will include a Plunk-branded footer in all emails. + +## Pay-as-you-go +After upgrading from the free tier, you will be charged per email sent at $0.001 per email. There are no monthly fees or commitments, you only pay for what you use. + +You are able to monitor your email usage and set billing limits per category in the billing tab of the project settings. + +## Special considerations +- Emails that contain an attachment will incur double the cost (e.g. 1 email with attachment = 2 emails for billing purposes) \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/campaigns-vs-workflows.mdx b/apps/wiki/content/docs/concepts/campaigns-vs-workflows.mdx deleted file mode 100644 index 8cce205..0000000 --- a/apps/wiki/content/docs/concepts/campaigns-vs-workflows.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Campaigns vs Workflows -description: Choose the right sending method -icon: GitCompare ---- - -## Quick comparison - -| Need | Use | -|------|-----| -| Password reset now | **API** `/v1/send` | -| Monthly newsletter | **Campaign** | -| Welcome series over 3 days | **Workflow** | -| Order confirmation | **API** `/v1/send` | -| Abandoned cart recovery | **Workflow** | - -## Transactional API - -Immediate one-off emails from your code. - -```javascript -await fetch('/v1/send', { - method: 'POST', - body: JSON.stringify({ - to: user.email, - subject: 'Reset your password', - body: `Click here: ${resetLink}` - }) -}); -``` - -## Campaigns - -One-time broadcasts to many contacts. Created in dashboard. - -- Send now or schedule -- Target all contacts, segments, or filters -- No code required - -## Workflows - -Automated multi-email sequences. - -- Triggered by events or segment changes -- Delays between emails -- Conditional logic (if/then) -- Re-entry control diff --git a/apps/wiki/content/docs/concepts/campaigns.mdx b/apps/wiki/content/docs/concepts/campaigns.mdx new file mode 100644 index 0000000..20b64c4 --- /dev/null +++ b/apps/wiki/content/docs/concepts/campaigns.mdx @@ -0,0 +1,15 @@ +--- +title: Campaigns +description: Broadcast to your contacts +icon: Megaphone +--- + +Campaigns are a one-time email sent to a group of contacts. They are typically used for newsletters, announcements, or promotions. + +## Targeting contacts +When you create a campaign, you can send it to all subscribed contacts, or target a specific segment you have created in the [Segments](/concepts/segments) section. + +## Sending a campaign +Once you have designed your campaign and selected the target audience, you can schedule it to be sent immediately or at a later time. You can also choose to send a test email to yourself or any other team member before sending it to your contacts. + +Campaigns will be queued and sent in the background. You can monitor the sending progress and view detailed analytics on opens, clicks, and bounces in the campaign detail page. Depending on the size of your audience, it may take some time for all emails to be sent. \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/contacts-and-data.mdx b/apps/wiki/content/docs/concepts/contacts-and-data.mdx deleted file mode 100644 index 11bb17d..0000000 --- a/apps/wiki/content/docs/concepts/contacts-and-data.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Contacts -description: Store and manage your audience -icon: Users ---- - -## Structure - -```json -{ - "id": "contact_abc123", - "email": "user@example.com", - "subscribed": true, - "data": { - "firstName": "Sarah", - "plan": "pro", - "mrr": 99 - } -} -``` - -## Adding contacts - -- **Dashboard:** Contacts → Add Contact -- **CSV import:** Contacts → Import -- **API:** `POST /contacts` -- **Events:** Auto-created when tracking events - -## Contact data - -The `data` field stores custom key-value pairs. - -**Best practices:** -- Use consistent naming (camelCase or snake_case) -- Store dates as ISO strings: `"2024-03-15T10:30:00Z"` -- Use numbers for numeric values (enables comparisons) - -## Localization - -The `locale` field in contact data overrides the project-wide language setting for that specific contact. - -**Where it's used:** -- Unsubscribe and preference center pages -- Email footer translations -- All contact-facing content - -**Example:** - -```javascript -// Set a contact's preferred language -POST /contacts -{ - "email": "user@example.com", - "data": { - "locale": "de" // German - } -} -``` - -**Via tracking:** - -```javascript -POST /v1/track -{ - "event": "signup", - "email": "user@example.com", - "data": { - "locale": "fr" // French - } -} -``` - -**Behavior:** -- If `locale` is set: uses contact's language -- If not set: uses project's default language -- Falls back to English if neither is set - -## Template variables - -Use `{{fieldName}}` in emails: - -```html -

Hello {{firstName}}!

-``` - -**Fallback:** `{{firstName ?? 'there'}}` - -**System-generated fields (always available):** -- `{{email}}` - Contact's email address -- `{{unsubscribeUrl}}` - Link to unsubscribe page -- `{{subscribeUrl}}` - Link to subscribe page -- `{{manageUrl}}` - Link to preference center - -**Special fields:** -- `{{locale}}` - Contact's language preference (user-settable, overrides project default) - -## Temporary data - -Data that won't save to contact: - -```javascript -data: { - resetCode: { value: 'ABC123', persistent: false } -} -``` - -Use for: one-time codes, tokens, session data. diff --git a/apps/wiki/content/docs/concepts/contacts.mdx b/apps/wiki/content/docs/concepts/contacts.mdx new file mode 100644 index 0000000..318d857 --- /dev/null +++ b/apps/wiki/content/docs/concepts/contacts.mdx @@ -0,0 +1,46 @@ +--- +title: Contacts +description: Manage and organize your contacts effectively +icon: Users +--- + +Contacts in Plunk represent an individual email recipient. Each contact has an identifier and is linked to an email address. + +## Adding contacts +Contacts can be added to your Plunk project in several ways: +- Using [/v1/track](/api-reference/public-api/trackEvent), when tracking an event for a contact that does not yet exist, Plunk will automatically create it. +- Using [/contacts](/api-reference/contacts/createContact), to create a single contact. +- Import through CSV +- Manually through the dashboard + +## Contact Data +You can associate custom data with each contact using key-value pairs. This data can be used for segmentation and personalization. + +### Data types +Contact data types are inferred based on the value provided: +| Type | Description | +|------|-------------| +| String | Any text value | +| Number | Numeric values, including integers and floats | +| Boolean | True or false values | +| Date | Date values in ISO 8601 format | + + +If you accidentally mix data types for a specific key, Plunk will default to treating the value as a string. + + +### Reserved keys +Certain keys are reserved by the system and automatically set by Plunk: +| Key | Description | +|-----|-------------| +| email | The contact's email address | +| createdAt | Timestamp of when the contact was created | +| updatedAt | Timestamp of the last update to the contact | +| subscribed | Boolean indicating if the contact is globally subscribed or not | + +### Special keys +| Key | Description | +|-----|-------------| +| locale | The contact's preferred locale in ISO 639 (e.g. 'en', 'fr', 'es'). Specifying the locale field on a contact will override the project-wide locale for contact-facing pages and email footers | \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/custom-domains.mdx b/apps/wiki/content/docs/concepts/custom-domains.mdx deleted file mode 100644 index 8c23261..0000000 --- a/apps/wiki/content/docs/concepts/custom-domains.mdx +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Custom Domains -description: Send from your own domain -icon: Globe ---- - -## Why use custom domains - -- Better deliverability -- Brand consistency -- Builds sender reputation - -## Setup - -1. Go to **Settings → Domains** -2. Click **Add Domain** -3. Enter your domain -4. Add the provided DNS records (DKIM, MX, TXT) -5. Wait for verification (usually 10-30 minutes, up to 48 hours) - -## Using your domain - -Specify in the `from` field when sending: - -```json -{ - "to": "customer@example.com", - "from": "orders@yourdomain.com", - "subject": "Order confirmed", - "body": "

Your order has been confirmed.

" -} -``` - -## Troubleshooting - -**Domain won't verify:** -- Check DNS records are correct -- Wait for DNS propagation (up to 48 hours) -- Remove conflicting DKIM records from other services - -**Emails going to spam:** -- Warm up new domains with small volumes first -- Monitor bounce and complaint rates diff --git a/apps/wiki/content/docs/concepts/events-and-triggers.mdx b/apps/wiki/content/docs/concepts/events-and-triggers.mdx deleted file mode 100644 index 3ae1a29..0000000 --- a/apps/wiki/content/docs/concepts/events-and-triggers.mdx +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Events -description: Track actions and trigger workflows -icon: Activity ---- - -## Overview - -Events track user actions. Use them to trigger workflows and update contact data. - -## Tracking - -```javascript -await fetch('/v1/track', { - method: 'POST', - headers: { - 'Authorization': 'Bearer pk_your_public_key' - }, - body: JSON.stringify({ - event: 'signed_up', - email: user.email, - data: { plan: 'pro' } - }) -}); -``` - -Creates/updates contact and records the event. - -## Keys - -- **Public key (pk_*):** Safe for client-side, only works with `/v1/track` -- **Secret key (sk_*):** Server-side only, full API access - -## Event data - -**Persistent (default):** Saved to contact -```javascript -data: { plan: 'premium' } -``` - -**Non-persistent:** Available only to workflow -```javascript -data: { - orderId: { value: 'order-123', persistent: false } -} -``` - -## Automatic events - -Plunk tracks these automatically: -- `email.sent`, `email.opened`, `email.clicked` -- `email.bounced`, `email.complained` -- `segment.entered`, `segment.exited` diff --git a/apps/wiki/content/docs/concepts/meta.json b/apps/wiki/content/docs/concepts/meta.json index 0f6ba09..761944f 100644 --- a/apps/wiki/content/docs/concepts/meta.json +++ b/apps/wiki/content/docs/concepts/meta.json @@ -1,14 +1,3 @@ { - "title": "Core Concepts", - "pages": [ - "contacts-and-data", - "templates-types", - "campaigns-vs-workflows", - "segments-and-filters", - "events-and-triggers", - "webhooks", - "custom-domains", - "attachments", - "troubleshooting" - ] + "pages": ["contacts", "segments", "workflows", "campaigns", "transactional-emails", "templates", "billing"] } diff --git a/apps/wiki/content/docs/concepts/segments-and-filters.mdx b/apps/wiki/content/docs/concepts/segments-and-filters.mdx deleted file mode 100644 index ce8c7d4..0000000 --- a/apps/wiki/content/docs/concepts/segments-and-filters.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Segments -description: Dynamic audience groups -icon: Funnel ---- - -## Overview - -Segments are dynamic groups based on filters. They update automatically when contact data changes. - -## Creating - -1. Go to **Segments** → **Create Segment** -2. Name your segment -3. Add filter conditions -4. Save - -## Operators - -| Operator | Example | -|----------|---------| -| `equals` | `plan equals "pro"` | -| `not equals` | `plan not equals "free"` | -| `contains` | `email contains "@gmail"` | -| `greater than` | `mrr greater than 100` | -| `less than` | `loginCount less than 5` | -| `exists` | `company exists` | - -## Combining filters - -- **AND:** All conditions must match -- **OR:** Any condition can match - -## Membership tracking - -Enable **Track membership changes** to: -- Trigger workflows on segment entry/exit -- Generate `segment.entered` and `segment.exited` events - -Only enable for segments used as workflow triggers. diff --git a/apps/wiki/content/docs/concepts/segments.mdx b/apps/wiki/content/docs/concepts/segments.mdx new file mode 100644 index 0000000..b572a32 --- /dev/null +++ b/apps/wiki/content/docs/concepts/segments.mdx @@ -0,0 +1,20 @@ +--- +title: Segments +description: Group and target your contacts with dynamic segments +icon: Layers +--- + +Segments in Plunk allow you to create dynamic groups of contacts based on [contact data](/concepts/contacts#contact-data) and events. + +## Creating segments +Segments can be created through the Plunk dashboard. When creating a segment, you can define multiple conditions that contacts must meet to be included in the segment. Plunk will automatically update the segment membership as contact data and events change. + +### Track Membership Changes +Plunk will automatically add and remove contacts from segments as their data and events change. When toggling on `Track membership changes`, Plunk will send an event to your webhook each time a contact is added or removed from the segment. + +These events will have the following name `segment.trial-users.entry` or `segment.trial-users.exit`, where `trial-users` is the segment's name. + +## Using segments +Segment can be used in various parts of Plunk, including: +- Targeting contacts in [email campaigns](/concepts/campaigns) +- Triggering workflows in [marketing automation](/concepts/workflows) \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/templates-types.mdx b/apps/wiki/content/docs/concepts/templates-types.mdx deleted file mode 100644 index 814d73f..0000000 --- a/apps/wiki/content/docs/concepts/templates-types.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Template Types -description: Marketing vs Transactional -icon: Mail ---- - -## Two types - -| Type | Sends to Unsubscribed? | Use For | -|------|------------------------|---------| -| **Marketing** | No | Newsletters, promotions | -| **Transactional** | Yes | Receipts, password resets | - -## Marketing - -Only sends to subscribed contacts. Automatically includes unsubscribe link. - -## Transactional - -Sends regardless of subscription status. Use only for essential emails. - -## Choosing - -Ask: "Would user be frustrated if they didn't receive this after unsubscribing?" - -- **Yes → Transactional** (password resets, order confirmations) -- **No → Marketing** (newsletters, promotions) - -## Variables - -```html -

Hello {{firstName}}!

-

Your {{plan}} plan renews on {{renewalDate}}.

-``` - -**Fallback:** `{{firstName ?? 'there'}}` diff --git a/apps/wiki/content/docs/concepts/templates.mdx b/apps/wiki/content/docs/concepts/templates.mdx new file mode 100644 index 0000000..c8824bb --- /dev/null +++ b/apps/wiki/content/docs/concepts/templates.mdx @@ -0,0 +1,24 @@ +--- +title: Templates +description: Create reusable email templates for your campaigns, workflows and transactional emails +icon: SwatchBook +--- + +Templates are stored in the Plunk dashboard and can be used in campaigns, workflows and transactional emails. + +## Designing templates +Templates can be created using the built-in editor or by uploading your own HTML. + +### Personalization +You can use contact data to personalize your templates by using the handlebars syntax `{{ key }}`, where `key` is the contact data key. + +### Previewing templates +You can preview your templates by selecting a contact in the preview window. This will allow you to see how the template will look for that specific contact, with their data populated. + +## Templates types +There are two types of templates in Plunk. Each type is treated at the same priority when sending emails, you should not pick one type over the other based on deliverability or performance. + +| Type | Description | +|------|-------------| +| Marketing | Automatically includes a Plunk-hosted unsubscribe page and footer. Will not be sent to contacts who are unsubscribed | +| Transactional | Does not include any way to unsubscribe. Will be sent to any contact, regardless of subscription state | \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/transactional-emails.mdx b/apps/wiki/content/docs/concepts/transactional-emails.mdx new file mode 100644 index 0000000..145e80c --- /dev/null +++ b/apps/wiki/content/docs/concepts/transactional-emails.mdx @@ -0,0 +1,13 @@ +--- +title: Transactional Emails +description: Send emails via API +icon: Send +--- + +Transactional emails are emails sent directly through the API. They are typically used for one-to-one communication, such as password resets, order confirmations, and notifications. + +## Sending with attachments +Plunk supports sending attachments with transactional emails. You can include up to 10 attachments per email, with a maximum total size of 10MB. Attachments should be base64 encoded and included in the `attachments` array when sending the email via the [/v1/email/send](/api-reference/public-api/sendEmail) endpoint. + +## Sending from a template +You can also send transactional emails using a [template](/concepts/templates) you have created in the dashboard. This allows you to reuse the same design and content for multiple emails, while still personalizing them with contact data. \ No newline at end of file diff --git a/apps/wiki/content/docs/concepts/troubleshooting.mdx b/apps/wiki/content/docs/concepts/troubleshooting.mdx deleted file mode 100644 index fca3be9..0000000 --- a/apps/wiki/content/docs/concepts/troubleshooting.mdx +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Troubleshooting -description: Common issues and solutions -icon: Bug ---- - -## Authentication - -**Invalid API key:** -- Secret keys start with `sk_`, public keys with `pk_` -- Use `Authorization: Bearer sk_your_secret_key` - -**Wrong key type:** -- Public keys (`pk_`) only work with `/v1/track` -- All other endpoints require secret keys (`sk_`) - -## Emails not sending - -1. **Billing limit reached** — Check Settings → Billing -2. **Template not found** — Verify template ID exists -3. **Contact unsubscribed** — Marketing templates skip unsubscribed contacts - -## Emails going to spam - -1. Set up a custom domain -2. Clean your list (remove bounces) -3. Warm up new domains gradually - -## Workflows not triggering - -1. **Workflow not enabled** — Check toggle is ON -2. **Wrong event name** — Names are case-sensitive -3. **Already entered** — If re-entry disabled, contact can only enter once - -## Rate limiting (429) - -- Wait and retry -- Batch multiple recipients in one request -- Spread requests over time - -## Request IDs - -Every API response includes `X-Request-ID` header. Include this when contacting support. - diff --git a/apps/wiki/content/docs/concepts/webhooks.mdx b/apps/wiki/content/docs/concepts/webhooks.mdx deleted file mode 100644 index f0b38fa..0000000 --- a/apps/wiki/content/docs/concepts/webhooks.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Webhooks -description: Send HTTP requests from workflows -icon: Webhook ---- - -## Overview - -Webhooks send HTTP requests to external services from within workflows. - -## Adding a webhook step - -1. Edit a workflow -2. Add a **Webhook** step -3. Configure URL, method, headers, and body - -## Configuration - -```json -{ - "url": "https://your-api.com/webhook", - "method": "POST", - "headers": { - "Authorization": "Bearer your_api_token" - }, - "body": { - "email": "{{email}}", - "firstName": "{{data.firstName}}" - } -} -``` - -**Methods:** POST, PUT, PATCH, GET, DELETE - -## Variables - -- `{{email}}`, `{{id}}` — Contact fields -- `{{data.fieldName}}` — Custom data -- `{{workflowName}}`, `{{now}}` — Workflow context - -## Error handling - -- Failed webhooks don't block the workflow -- Errors logged in execution details -- 30 second timeout -- No automatic retries - diff --git a/apps/wiki/content/docs/concepts/workflows.mdx b/apps/wiki/content/docs/concepts/workflows.mdx new file mode 100644 index 0000000..7767997 --- /dev/null +++ b/apps/wiki/content/docs/concepts/workflows.mdx @@ -0,0 +1,41 @@ +--- +title: Workflows +description: Set up automated email sequences and trigger them from your apps +icon: Workflow +--- + +Workflows in Plunk allow you to create automated email sequences that can be triggered based on events you send using the API. + +## Prerequisites for workflows +### Sending an event +Workflows are triggered by sending events to Plunk using the [/v1/track](/api-reference/public-api/trackEvent) endpoint. When sending an event, you can specify the contact it is associated with and include any relevant data. + +### Creating a template +Before setting up the workflow, ensure you have created a template that will be used for the emails sent by the workflow. You can create templates in the [Templates](/concepts/templates) section of the dashboard. + +## Creating a workflow +To create a workflow, navigate to the [Workflows](/concepts/workflows) section of the dashboard and create a workflow. + + +When creating a workflow, you can not change the trigger event after the workflow has been created. Make sure to choose the correct event name that will trigger the workflow. + + +### Defining workflow steps +Workflows consists of multiple steps that define the sequence of actions to be taken. + +| Step Type | Description | +|-----------|-------------| +| Send Email | Sends an email to the contact using a specified template. You can customize the email content using variables from the event data. | +| Delay | Pauses the workflow for a specified duration before proceeding to the next step. | +| Wait for Event | Pauses the workflow until a specified event is received for the contact. You can also set a timeout duration to proceed if the event is not received within that time. | +| Condition | Evaluates a condition based on the event data or contact data and branches the workflow accordingly. | +| Webhook | Sends a webhook to a specified URL with the event and contact data. | +| Update Contact | Updates the contact's data with specified key-value pairs. | +| Exit | Terminates the workflow for the contact. | + +## Managing workflow executions +You can monitor and manage contacts going through workflows in the executions tab of the workflow detail page. Here you can see the status of each execution, cancel a specific execution or all executions. + +A workflow will be locked while there are active executions. If you want to make changes to a running workflow you will either need to pause it and wait for all executions to complete, or cancel all active executions. \ No newline at end of file diff --git a/apps/wiki/content/docs/getting-started/authentication.mdx b/apps/wiki/content/docs/getting-started/authentication.mdx deleted file mode 100644 index da5fba1..0000000 --- a/apps/wiki/content/docs/getting-started/authentication.mdx +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: Authentication -description: Understanding API keys and authentication ---- - -## Two types of API keys - -Each project has two API keys for different purposes: - -### Secret Key (sk_*) - -**Use for:** All server-side API calls - -- Required for `/v1/send` (sending emails) -- Required for all dashboard API endpoints (contacts, campaigns, templates, etc.) -- Can access and modify all project data -- **Never expose in client-side code** - -**Example:** -```javascript -// Server-side only -fetch('{{API_URL}}/v1/send', { - method: 'POST', - headers: { - 'Authorization': 'Bearer sk_your_secret_key', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - to: 'user@example.com', - subject: 'Hello', - body: '

Your order is ready!

' - }) -}); -``` - -### Public Key (pk_*) - -**Use for:** Client-side event tracking only - -- Works **only** with `/v1/track` endpoint -- Cannot send emails or access any other endpoints -- Safe to include in frontend JavaScript -- Use for tracking user behavior from web browsers or mobile apps - -**Example:** -```javascript -// Client-side safe -fetch('{{API_URL}}/v1/track', { - method: 'POST', - headers: { - 'Authorization': 'Bearer pk_your_public_key', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - email: 'user@example.com', - event: 'button_clicked', - data: { button: 'signup' } - }) -}); -``` - -## Finding your API keys - -1. Log into [Plunk dashboard]({{DASHBOARD_URL}}) -2. Select your project -3. Go to **Settings > API Keys** -4. Copy the key you need - -## Using API keys - -All authenticated requests use the `Authorization` header with Bearer token format: - -```bash -Authorization: Bearer sk_your_secret_key -``` - -### cURL example - -```bash -curl -X POST {{API_URL}}/v1/send \ - -H "Authorization: Bearer sk_your_secret_key" \ - -H "Content-Type: application/json" \ - -d '{"to": "user@example.com", "subject": "Test", "body": "Hello"}' -``` - -### Node.js example - -```javascript -const PLUNK_SECRET_KEY = process.env.PLUNK_SECRET_KEY; - -const response = await fetch('{{API_URL}}/v1/send', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${PLUNK_SECRET_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - to: 'user@example.com', - subject: 'Test', - body: 'Hello' - }) -}); -``` - -### Python example - -```python -import os -import requests - -PLUNK_SECRET_KEY = os.environ.get('PLUNK_SECRET_KEY') - -response = requests.post( - '{{API_URL}}/v1/send', - headers={ - 'Authorization': f'Bearer {PLUNK_SECRET_KEY}', - 'Content-Type': 'application/json' - }, - json={ - 'to': 'user@example.com', - 'subject': 'Test', - 'body': 'Hello' - } -) -``` - -## Security best practices - -### Store secret keys securely - -Never commit secret keys to version control. Use environment variables: - -```bash -# .env file (add to .gitignore) -PLUNK_SECRET_KEY=sk_your_secret_key -``` - -### Rotate compromised keys - -If a secret key is exposed: - -1. Go to **Settings > API Keys** -2. Click **Regenerate Secret Key** -3. Update your application with the new key -4. The old key stops working immediately - -### Use the right key for the job - -- **Sending emails from your backend?** → Use secret key -- **Tracking events from frontend?** → Use public key -- **Managing contacts via API?** → Use secret key -- **Building a workflow dashboard?** → Use secret key - -When in doubt, if it's not `/v1/track`, you need the secret key. - -## Next Steps - -Now that you're authenticated, you can: - -- [Send your first email](/getting-started/quick-start) -- [Explore the API](/api-reference/overview) -- [Manage contacts](/guides/contacts) -- [Create campaigns](/guides/campaigns) diff --git a/apps/wiki/content/docs/getting-started/introduction.mdx b/apps/wiki/content/docs/getting-started/introduction.mdx deleted file mode 100644 index 5c06361..0000000 --- a/apps/wiki/content/docs/getting-started/introduction.mdx +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: Introduction -description: Get started with Plunk ---- - -## What is Plunk? - -Plunk is an open-source email platform built on AWS SES. It provides a complete solution for transactional emails, marketing campaigns, and automated workflows—everything you need to manage email communications at scale. - -## What you can build - -### Send transactional emails -Send password resets, order confirmations, and notifications via API with template support and variable substitution. - -```javascript -await fetch('{{API_URL}}/v1/send', { - method: 'POST', - headers: { - 'Authorization': 'Bearer sk_your_secret_key', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - to: 'user@example.com', - subject: 'Welcome {{name}}!', - body: '

Hello {{name}}

Thanks for signing up.

', - data: { name: 'John' } - }) -}); -``` - -### Run marketing campaigns -Send newsletters and product updates to segments of your audience. Schedule sends or deliver immediately. - -### Automate email sequences -Build drip campaigns, onboarding flows, and behavior-triggered emails with the visual workflow builder. - -## Core concepts - -**Contacts** — People in your audience. Each has an email, subscription status, and custom data fields. - -**Templates** — Reusable email designs. Marketing templates respect subscription status; transactional templates always send. - -**Segments** — Dynamic groups based on contact data. Update automatically as data changes. - -**Campaigns** — One-time email broadcasts to all contacts, a segment, or filtered audience. - -**Workflows** — Automated sequences with delays, conditions, and event triggers. - -**Events** — Track user actions (signups, purchases) to trigger workflows and build segments. - -## Authentication - -Each project has two API keys: - -- **Secret Key (sk_*)** — Server-side only. Required for all endpoints except event tracking. -- **Public Key (pk_*)** — Client-side safe. Only works with `/v1/track` for event tracking. - -Never expose secret keys in frontend code. - -## Next steps - -- [Quick Start](/getting-started/quick-start) — Send your first email in 5 minutes -- [Authentication](/getting-started/authentication) — Understand API keys -- [API Reference](/api-reference/overview) — Explore all endpoints diff --git a/apps/wiki/content/docs/getting-started/meta.json b/apps/wiki/content/docs/getting-started/meta.json deleted file mode 100644 index 20ceb2f..0000000 --- a/apps/wiki/content/docs/getting-started/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Getting Started", - "pages": ["introduction", "authentication", "quick-start"] -} diff --git a/apps/wiki/content/docs/getting-started/quick-start.mdx b/apps/wiki/content/docs/getting-started/quick-start.mdx deleted file mode 100644 index 4907b65..0000000 --- a/apps/wiki/content/docs/getting-started/quick-start.mdx +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: Quick Start -description: Send your first email in 5 minutes ---- - -## Send your first email - -### 1. Get your API key - -1. Sign up at [Plunk dashboard]({{DASHBOARD_URL}}) -2. Create or select a project -3. Go to **Settings > API Keys** -4. Copy your **Secret Key** (starts with `sk_`) - -### 2. Send an email - -```bash -curl -X POST {{API_URL}}/v1/send \ - -H "Authorization: Bearer sk_your_secret_key" \ - -H "Content-Type: application/json" \ - -d '{ - "to": "recipient@example.com", - "subject": "Hello from Plunk", - "body": "

It works!

Your first email via Plunk.

" - }' -``` - -Response: - -```json -{ - "success": true, - "emails": [{ - "contact": { - "id": "contact_abc123", - "email": "recipient@example.com" - }, - "email": "email_xyz789" - }], - "timestamp": "2024-03-15T10:30:00.000Z" -} -``` - -The email is queued and delivers within seconds. - -## Use variables - -Make emails dynamic with template variables: - -```bash -curl -X POST {{API_URL}}/v1/send \ - -H "Authorization: Bearer sk_your_secret_key" \ - -H "Content-Type: application/json" \ - -d '{ - "to": "user@example.com", - "subject": "Welcome {{firstName}}!", - "body": "

Hello {{firstName}}

Welcome to {{companyName}}.

", - "data": { - "firstName": "Sarah", - "companyName": "Acme Inc" - } - }' -``` - -Variables use `{{variableName}}` syntax and pull from the `data` object. - -## Send to multiple recipients - -Pass an array to send the same email to multiple people: - -```javascript -{ - "to": ["user1@example.com", "user2@example.com", "user3@example.com"], - "subject": "Team update", - "body": "

Check out our new features!

" -} -``` - -Each recipient gets their own email with personalized data if provided. - -## Use saved templates - -Create reusable templates in the dashboard, then reference them by ID: - -```bash -curl -X POST {{API_URL}}/v1/send \ - -H "Authorization: Bearer sk_your_secret_key" \ - -H "Content-Type: application/json" \ - -d '{ - "to": "user@example.com", - "template": "welcome-email-template-id", - "data": { - "firstName": "Sarah", - "verificationUrl": "https://example.com/verify/abc123" - } - }' -``` - -The template's subject, body, and sender settings are used automatically. Your `data` fills in the template variables. - -## Track events - -Track user actions to trigger workflows and build segments: - -```bash -curl -X POST {{API_URL}}/v1/track \ - -H "Authorization: Bearer pk_your_public_key" \ - -H "Content-Type: application/json" \ - -d '{ - "email": "user@example.com", - "event": "signed_up", - "data": { - "plan": "pro", - "source": "landing_page" - } - }' -``` - -This creates or updates the contact and tracks the event. Use events to trigger automated workflows. - -## What's next - -**Set up workflows** — Build automated email sequences with [workflows](/guides/workflows) - -**Manage contacts** — Import and segment your audience with [contacts](/guides/contacts) - -**Send campaigns** — Broadcast to your entire list with [campaigns](/guides/campaigns) - -**Track engagement** — Monitor opens and clicks with [analytics](/guides/analytics) diff --git a/apps/wiki/content/docs/guides/api-keys.mdx b/apps/wiki/content/docs/guides/api-keys.mdx new file mode 100644 index 0000000..bc3ae69 --- /dev/null +++ b/apps/wiki/content/docs/guides/api-keys.mdx @@ -0,0 +1,19 @@ +--- +title: API Keys +description: Manage your API keys and understand their usage +icon: Key +--- + +Each project has two unique API keys. A public key and a secret key. These keys are used to authenticate requests made to Plunk's API. + +## Public Key +The public API key can only be used with the [/v1/track](/api-reference/public-api/trackEvent) endpoint to track events. This key can be safely exposed in client-side applications. + +## Secret Key +The secret API key can be used with all other endpoints in Plunk's API. This key should be kept confidential and not exposed in client-side applications. + +If this key is compromised, a malicious actor could read and modify your project data, send emails, and perform other actions on your behalf. + +## Regenerating API Keys +If you believe your API keys have been compromised, you can regenerate them in the project settings. +Keep in mind that regenerating an API key will invalidate both keys, so make sure to update your applications with the new key. \ No newline at end of file diff --git a/apps/wiki/content/docs/guides/list-hygiene.mdx b/apps/wiki/content/docs/guides/list-hygiene.mdx new file mode 100644 index 0000000..9c85013 --- /dev/null +++ b/apps/wiki/content/docs/guides/list-hygiene.mdx @@ -0,0 +1,31 @@ +--- +title: List Hygiene +description: Understand and maintain a healthy email list +icon: ShieldCheck +--- + +Plunk automatically monitors the bounce and complaint rates of your emails to help maintain a health sender reputation. High bounce or complaint rates can negatively impact your deliverability and may lead to your account being suspended. + +## Bounce Management + +A bounce occurs when an email cannot be delivered to the recipient's inbox. When an email bounces, Plunk will automatically unsubscribe the contact and also send an event on the contact for `email.bounced`. + +### Types of Bounces +| Type | Description | +|------|-------------| +| Hard Bounce | Permanent delivery failure (e.g., invalid email address) | +| Soft Bounce | Temporary delivery failure (e.g., mailbox full) | + +### Preventing bounces +- [Verify email addresses](/api-reference/public-api/verifyEmail) +- Regularly clean your email list +- Use double opt-in for subscriptions + +## Complaint Management + +A complaint occurs when a recipient marks your email as spam. When a complaint is received, Plunk will automatically unsubscribe the contact and send an event on the contact for `email.complaint`. + +### Preventing complaints +- Ensure your emails are relevant and valuable to your audience +- Include a clear unsubscribe link in every email +- Monitor your email frequency to avoid overwhelming your contacts \ No newline at end of file diff --git a/apps/wiki/content/docs/guides/localization.mdx b/apps/wiki/content/docs/guides/localization.mdx new file mode 100644 index 0000000..7e346a8 --- /dev/null +++ b/apps/wiki/content/docs/guides/localization.mdx @@ -0,0 +1,15 @@ +--- +title: Localization +description: Support for multiple languages and regions +icon: Globe +--- + +Localization in Plunk allows you to configure the language of the unsubscribe footer and contact-facing pages (subscribe, unsubscribe, manage) to better suit your audience. + +## Configuring Localization +You can set the default language for your project in the project settings. + +## Overriding language per contact +You can override the default language for individual contacts by setting the `locale` field in the contact data. This field should contain a valid [ISO 639](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code (e.g., 'en' for English, 'fr' for French, 'es' for Spanish). + +When sending emails, Plunk will use the contact's specified language if available; otherwise, it will fall back to the project's default language. \ No newline at end of file diff --git a/apps/wiki/content/docs/guides/meta.json b/apps/wiki/content/docs/guides/meta.json new file mode 100644 index 0000000..ae716c1 --- /dev/null +++ b/apps/wiki/content/docs/guides/meta.json @@ -0,0 +1,3 @@ +{ + "pages": ["list-hygiene", "verifying-domains", "tracking", "api-keys", "localization"] +} diff --git a/apps/wiki/content/docs/guides/tracking.mdx b/apps/wiki/content/docs/guides/tracking.mdx new file mode 100644 index 0000000..fcea473 --- /dev/null +++ b/apps/wiki/content/docs/guides/tracking.mdx @@ -0,0 +1,32 @@ +--- +title: Tracking +description: Tracking for opens and clicks in your emails +icon: HatGlasses +--- + +Plunk provides built-in tracking for email opens and link clicks. This allows you to monitor the engagement of your emails and gain insights into how your contacts interact with your content. + +## Open tracking + +When open tracking is enabled, Plunk includes a small, invisible tracking pixel in your emails. When a contact opens the email, the pixel is loaded, and Plunk records the open event. + +### Considerations +- Open tracking relies on the loading of images in the email client. If a contact has images disabled, the open event may not be recorded. +- Some email clients may pre-load images, which can result in false open events. + +## Click tracking + +Click tracking is enabled by default for all emails sent through Plunk. When a contact clicks on a link in the email, Plunk records the click event and tracks which link was clicked. + +### Considerations +- Click tracking works by rewriting the URLs in your email to point to Plunk's tracking servers +- Some email clients or security software may block tracking links, which can result in missed click events + +## Configuration +There are three levels of configuration for tracking: + +| Level | Description | +|-------|-------------| +| Enabled | Tracking is enabled for all emails | +| Disabled | Tracking is disabled for all emails | +| Marketing only | Tracking is enabled only for marketing emails (templates and campaigns) | \ No newline at end of file diff --git a/apps/wiki/content/docs/guides/verifying-domains.mdx b/apps/wiki/content/docs/guides/verifying-domains.mdx new file mode 100644 index 0000000..5addeee --- /dev/null +++ b/apps/wiki/content/docs/guides/verifying-domains.mdx @@ -0,0 +1,17 @@ +--- +title: Verifying domains +description: Ensure your emails reach the inbox by verifying your sending domains +icon: Globe +--- + +Verifying your domain is a requirement to send emails through Plunk. Domain verification helps improve email deliverability and ensures that your emails are less likely to be marked as spam. + +## Verifying a domain + +You can verify a domain by adding it in the domain tab of the project settings. Once added, Plunk will provide you with the necessary DNS records to add to your domain's DNS settings. +Once you have added the DNS records, it may take some time for the changes to propagate. You can check the verification status in the domain tab of the project settings. + +### DNS Records +- 3 CNAME records for DKIM (DomainKeys Identified Mail) to authenticate your emails. +- 1 TXT record for SPF (Sender Policy Framework) to specify which mail servers are authorized +- 1 MX record to handle bounces and feedback loops diff --git a/apps/wiki/content/docs/index.mdx b/apps/wiki/content/docs/index.mdx index ed89e08..192d04a 100644 --- a/apps/wiki/content/docs/index.mdx +++ b/apps/wiki/content/docs/index.mdx @@ -6,51 +6,51 @@ icon: House ## What is Plunk? -Plunk is an open-source email platform designed for developers who need a powerful, scalable solution for transactional and marketing emails. Built with modern technologies and designed to handle millions of contacts, Plunk provides everything you need to manage your email communications. +Plunk is an open-source email platform built on top of AWS SES. It is designed for developers who need a powerful, scalable solution for transactional and marketing emails. Built with modern technologies and designed to handle millions of contacts, Plunk provides everything you need to manage your email communications. ## Key Features - + Send transactional emails via API with template support and variable substitution - + Create and send one-time email broadcasts to your contacts with advanced scheduling - + Build automated email sequences with visual workflow builder and conditional logic - + Manage millions of contacts with custom fields and CSV import capabilities - + Create dynamic audience segments based on contact data and behavior - - Track events and use them to trigger workflows and segment audiences - - + Create reusable email templates for transactional and marketing emails - - Track email performance with detailed analytics on opens, clicks, and more + + Track events and use them to trigger workflows and segment audiences + + + Understand Plunk's billing model and pricing ## Quick Links - - Learn how to set up Plunk and send your first email - Explore the complete API documentation Deploy Plunk on your own infrastructure - - Step-by-step guides for common use cases + + Learn how to verify your domains for sending emails + + + Manage your API keys and authentication diff --git a/apps/wiki/content/docs/meta.json b/apps/wiki/content/docs/meta.json index 4492023..e213543 100644 --- a/apps/wiki/content/docs/meta.json +++ b/apps/wiki/content/docs/meta.json @@ -1,14 +1,9 @@ { "pages": [ "index", - "---Getting Started---", - "getting-started", - "---Tutorials---", - "tutorials", - "---Core Concepts---", + "---Docs---", "concepts", - "---Automation---", - "automation-patterns", + "guides", "---API Reference---", "api-reference", "---Self-Hosting---", diff --git a/apps/wiki/content/docs/tutorials/first-transactional-email.mdx b/apps/wiki/content/docs/tutorials/first-transactional-email.mdx deleted file mode 100644 index 46e0dc4..0000000 --- a/apps/wiki/content/docs/tutorials/first-transactional-email.mdx +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Send Your First Email -description: Send a transactional email in 5 minutes -icon: Mail ---- - -## 1. Get your API key - -1. Go to **Settings → General** -2. Copy your **Secret Key** (starts with `sk_`) - -## 2. Send the email - -```bash -curl -X POST {{API_URL}}/v1/send \ - -H "Authorization: Bearer sk_your_secret_key" \ - -H "Content-Type: application/json" \ - -d '{ - "to": "user@example.com", - "subject": "Hello from Plunk", - "body": "

Your first email!

" - }' -``` - -## 3. With variables - -```javascript -await fetch('{{API_URL}}/v1/send', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.PLUNK_SECRET_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - to: user.email, - subject: 'Reset your password', - body: '

Hi {{name}}, click here: Reset

', - name: user.name, - resetLink: `https://app.com/reset/${token}` - }) -}); -``` - -## 4. Using a template - -1. Create template in **Templates → Create Template** -2. Send using template ID: - -```javascript -{ - to: user.email, - template: 'clx123abc456', - name: user.name, - resetLink: resetUrl -} -``` diff --git a/apps/wiki/content/docs/tutorials/meta.json b/apps/wiki/content/docs/tutorials/meta.json deleted file mode 100644 index 4daa083..0000000 --- a/apps/wiki/content/docs/tutorials/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Tutorials", - "pages": ["first-transactional-email", "welcome-series-workflow", "newsletter-campaign", "segment-based-targeting"] -} diff --git a/apps/wiki/content/docs/tutorials/newsletter-campaign.mdx b/apps/wiki/content/docs/tutorials/newsletter-campaign.mdx deleted file mode 100644 index 9230258..0000000 --- a/apps/wiki/content/docs/tutorials/newsletter-campaign.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Send a Newsletter -description: Broadcast an email to your audience -icon: Send ---- - -## 1. Create a campaign - -1. Go to **Campaigns → Create Campaign** -2. Name: `March Newsletter` - -## 2. Write your email - -- **From:** Your verified domain -- **Subject:** `March updates you'll love` -- Write your content - -## 3. Select audience - -Choose one: -- **All contacts:** Everyone subscribed -- **Segment:** A saved segment -- **Filtered:** Custom filters for this campaign - -## 4. Test - -1. Click **Send Test** -2. Check your inbox -3. Verify links and content - -## 5. Send - -- **Send Now:** Starts immediately -- **Schedule:** Pick date and time - -## 6. Monitor - -View stats at **Campaigns → Your campaign**: -- Sent, delivered, opened, clicked, bounced diff --git a/apps/wiki/content/docs/tutorials/segment-based-targeting.mdx b/apps/wiki/content/docs/tutorials/segment-based-targeting.mdx deleted file mode 100644 index 6f793a4..0000000 --- a/apps/wiki/content/docs/tutorials/segment-based-targeting.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Target with Segments -description: Send to specific audiences -icon: Users ---- - -## 1. Create a segment - -1. Go to **Segments → Create Segment** -2. Name: `Premium Users` - -## 2. Add filters - -Example: Premium subscribers - -- Field: `plan` -- Operator: `equals` -- Value: `premium` - -Add more conditions with AND/OR. - -## 3. Use in campaigns - -1. Create campaign -2. Audience: Select **Segment** -3. Choose `Premium Users` - -## 4. Use in workflows - -1. Create workflow -2. Trigger: **Segment entry** -3. Choose your segment -4. Enable **Track membership changes** on the segment diff --git a/apps/wiki/content/docs/tutorials/welcome-series-workflow.mdx b/apps/wiki/content/docs/tutorials/welcome-series-workflow.mdx deleted file mode 100644 index 8c63103..0000000 --- a/apps/wiki/content/docs/tutorials/welcome-series-workflow.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Build a Welcome Series -description: Create a 3-email onboarding workflow -icon: Workflow ---- - -## What you'll build - -- Day 0: Welcome email (immediate) -- Day 1: Feature tour -- Day 3: Help offer - -## 1. Create templates - -Create 3 templates in **Templates → Create Template**: -- `Welcome` (use `{{name}}` for personalization) -- `Feature Tour` -- `Help Offer` - -## 2. Create the workflow - -1. Go to **Workflows → Create Workflow** -2. Name: `Welcome Series` -3. Trigger: Event `user_signed_up` -4. Allow Re-entry: No - -## 3. Build the flow - -``` -[Trigger: user_signed_up] - ↓ -[Send: Welcome] - ↓ -[Delay: 1 day] - ↓ -[Send: Feature Tour] - ↓ -[Delay: 2 days] - ↓ -[Send: Help Offer] - ↓ -[Exit] -``` - -## 4. Enable the workflow - -Toggle the workflow ON. - -## 5. Track signups - -Add to your app: - -```javascript -await fetch('{{API_URL}}/v1/track', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - event: 'user_signed_up', - email: user.email, - data: { name: user.name } - }) -}); -``` - -Use your **Public Key** (starts with `pk_`). diff --git a/apps/wiki/lib/cn.ts b/apps/wiki/lib/cn.ts new file mode 100644 index 0000000..ba66fd2 --- /dev/null +++ b/apps/wiki/lib/cn.ts @@ -0,0 +1 @@ +export { twMerge as cn } from 'tailwind-merge'; diff --git a/apps/wiki/lib/get-llm-text.ts b/apps/wiki/lib/get-llm-text.ts new file mode 100644 index 0000000..d573d70 --- /dev/null +++ b/apps/wiki/lib/get-llm-text.ts @@ -0,0 +1,10 @@ +import {source} from '@/lib/source'; +import type {InferPageType} from 'fumadocs-core/source'; + +export async function getLLMText(page: InferPageType) { + const processed = await page.data.getText('processed'); + + return `# ${page.data.title} (${page.url}) + +${processed}`; +} diff --git a/apps/wiki/source.config.ts b/apps/wiki/source.config.ts index 92ca092..765d0f1 100644 --- a/apps/wiki/source.config.ts +++ b/apps/wiki/source.config.ts @@ -4,6 +4,11 @@ import {remarkReplaceEnv} from './lib/remark-replace-env.mjs'; // Options: https://fumadocs.vercel.app/docs/mdx/collections#define-docs export const docs = defineDocs({ dir: 'content/docs', + docs: { + postprocess: { + includeProcessedMarkdown: true, + }, + }, }); export default defineConfig({