Update wiki
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
---
|
||||
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": "[email protected]",
|
||||
"subject": "Your Invoice",
|
||||
"body": "<p>Invoice attached.</p>",
|
||||
"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');
|
||||
```
|
||||
|
||||
@@ -1,37 +1,26 @@
|
||||
---
|
||||
title: Campaigns vs Workflows
|
||||
description: Choose the right tool for sending emails
|
||||
description: Choose the right sending method
|
||||
icon: GitCompare
|
||||
---
|
||||
|
||||
## When to use each
|
||||
## Quick comparison
|
||||
|
||||
**Transactional API** (`/v1/send`) — Immediate one-off emails from your code
|
||||
- Password resets, confirmations, receipts
|
||||
- Triggered directly by user actions
|
||||
- Instant delivery
|
||||
|
||||
**Campaigns** — One-time broadcasts to many contacts
|
||||
- Newsletters, announcements, promotions
|
||||
- Created in dashboard, send now or schedule
|
||||
- No code required
|
||||
|
||||
**Workflows** — Automated multi-email sequences
|
||||
- Onboarding series, abandoned cart, trial reminders
|
||||
- Triggered by events, segment changes, or schedules
|
||||
- Delays, conditions, multiple emails
|
||||
| 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
|
||||
|
||||
Send emails directly from your application code.
|
||||
Immediate one-off emails from your code.
|
||||
|
||||
```javascript
|
||||
await fetch('{{API_URL}}/v1/send', {
|
||||
await fetch('/v1/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
to: user.email,
|
||||
subject: 'Reset your password',
|
||||
@@ -40,90 +29,19 @@ await fetch('{{API_URL}}/v1/send', {
|
||||
});
|
||||
```
|
||||
|
||||
**Use for:**
|
||||
- Emails that must send immediately
|
||||
- One-to-one triggered emails
|
||||
- Context-specific data (reset tokens, order IDs)
|
||||
|
||||
## Campaigns
|
||||
|
||||
Create and send broadcasts in the dashboard.
|
||||
One-time broadcasts to many contacts. Created in dashboard.
|
||||
|
||||
1. Create campaign
|
||||
2. Write email content
|
||||
3. Select audience (all contacts, segment, or filter)
|
||||
4. Send now or schedule
|
||||
|
||||
**Use for:**
|
||||
- One-time sends to many people
|
||||
- Scheduled announcements
|
||||
- Manual email sends
|
||||
|
||||
**Can't do:**
|
||||
- Automation or triggers
|
||||
- Multi-step sequences
|
||||
- Delays between emails
|
||||
- Send now or schedule
|
||||
- Target all contacts, segments, or filters
|
||||
- No code required
|
||||
|
||||
## Workflows
|
||||
|
||||
Build automated sequences with the visual workflow builder.
|
||||
Automated multi-email sequences.
|
||||
|
||||
**Example workflow:**
|
||||
```
|
||||
[Trigger: user_signed_up]
|
||||
↓
|
||||
[Send Email: Welcome]
|
||||
↓
|
||||
[Delay: 24 hours]
|
||||
↓
|
||||
[Send Email: Feature tour]
|
||||
↓
|
||||
[Delay: 48 hours]
|
||||
↓
|
||||
[Send Email: Help offer]
|
||||
```
|
||||
|
||||
**Use for:**
|
||||
- Multi-email sequences
|
||||
- Time-delayed follow-ups
|
||||
- Event-triggered automation
|
||||
- Triggered by events or segment changes
|
||||
- Delays between emails
|
||||
- Conditional logic (if/then)
|
||||
|
||||
**Setup required:**
|
||||
- Track events from your app via `/v1/track`
|
||||
- Create workflow in dashboard
|
||||
- Enable workflow
|
||||
|
||||
## Decision matrix
|
||||
|
||||
| Need | Use |
|
||||
|------|-----|
|
||||
| Send password reset now | Transactional API |
|
||||
| Send monthly newsletter | Campaign |
|
||||
| Send welcome series over 3 days | Workflow |
|
||||
| Send order confirmation | Transactional API |
|
||||
| Send announcement to all users | Campaign |
|
||||
| Send trial reminder 3 days before expiration | Workflow |
|
||||
| Send receipt after payment | Transactional API |
|
||||
| Send seasonal promotion | Campaign |
|
||||
| Send abandoned cart recovery (1hr + 24hr) | Workflow |
|
||||
|
||||
## Using them together
|
||||
|
||||
Most apps use all three:
|
||||
|
||||
**SaaS example:**
|
||||
- **Transactional**: Password resets, email verification
|
||||
- **Campaigns**: Monthly product updates
|
||||
- **Workflows**: Trial onboarding, churn prevention
|
||||
|
||||
**E-commerce example:**
|
||||
- **Transactional**: Order confirmations, shipping updates
|
||||
- **Campaigns**: Weekly deals newsletter
|
||||
- **Workflows**: Abandoned cart, review requests
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Send a transactional email](/tutorials/first-transactional-email)
|
||||
- [Create your first workflow](/tutorials/welcome-series-workflow)
|
||||
- [Build a campaign](/tutorials/newsletter-campaign)
|
||||
- Re-entry control
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
---
|
||||
title: Contacts and Data
|
||||
title: Contacts
|
||||
description: Store and manage your audience
|
||||
icon: Users
|
||||
---
|
||||
|
||||
## What are contacts
|
||||
|
||||
Contacts are people in your email list. Each contact has:
|
||||
|
||||
- **Email** — Unique identifier
|
||||
- **Subscription status** — Subscribed or unsubscribed
|
||||
- **Custom data** — Any fields you need
|
||||
|
||||
## Data structure
|
||||
## Structure
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -27,188 +19,42 @@ Contacts are people in your email list. Each contact has:
|
||||
}
|
||||
```
|
||||
|
||||
The `data` field stores custom information as key-value pairs.
|
||||
## Adding contacts
|
||||
|
||||
## Data types
|
||||
- **Dashboard:** Contacts → Add Contact
|
||||
- **CSV import:** Contacts → Import
|
||||
- **API:** `POST /contacts`
|
||||
- **Events:** Auto-created when tracking events
|
||||
|
||||
**Strings:**
|
||||
```json
|
||||
{ "firstName": "Sarah", "company": "Acme Inc" }
|
||||
## 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)
|
||||
|
||||
## Template variables
|
||||
|
||||
Use `{{fieldName}}` in emails:
|
||||
|
||||
```html
|
||||
<p>Hello {{firstName}}!</p>
|
||||
```
|
||||
|
||||
**Numbers:**
|
||||
```json
|
||||
{ "mrr": 99, "loginCount": 15 }
|
||||
```
|
||||
**Fallback:** `{{firstName ?? 'there'}}`
|
||||
|
||||
Store as numbers for `greaterThan`/`lessThan` comparisons.
|
||||
**Reserved:** `{{email}}`, `{{id}}`
|
||||
|
||||
**Booleans:**
|
||||
```json
|
||||
{ "verified": true, "newsletter": false }
|
||||
```
|
||||
## Temporary data
|
||||
|
||||
**Dates:**
|
||||
```json
|
||||
{ "signupDate": "2024-03-15T10:30:00Z" }
|
||||
```
|
||||
Data that won't save to contact:
|
||||
|
||||
Use ISO 8601 format.
|
||||
|
||||
**Arrays:**
|
||||
```json
|
||||
{ "tags": ["vip", "enterprise"] }
|
||||
```
|
||||
|
||||
**Objects:**
|
||||
```json
|
||||
{
|
||||
"address": {
|
||||
"city": "San Francisco",
|
||||
"country": "US"
|
||||
}
|
||||
```javascript
|
||||
data: {
|
||||
resetCode: { value: 'ABC123', persistent: false }
|
||||
}
|
||||
```
|
||||
|
||||
Access nested fields: `address.country`
|
||||
|
||||
## Creating contacts
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
curl -X POST {{API_URL}}/contacts \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "[email protected]",
|
||||
"subscribed": true,
|
||||
"data": {
|
||||
"firstName": "Sarah",
|
||||
"plan": "pro"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### Automatic upsert
|
||||
|
||||
If email exists, updates instead of creating duplicate.
|
||||
|
||||
```javascript
|
||||
// First call
|
||||
POST /contacts { email: "[email protected]", data: { plan: "free" } }
|
||||
|
||||
// Second call - updates same contact
|
||||
POST /contacts { email: "[email protected]", data: { mrr: 99 } }
|
||||
|
||||
// Result: { plan: "free", mrr: 99 }
|
||||
```
|
||||
|
||||
Data merges automatically.
|
||||
|
||||
### Via event tracking
|
||||
|
||||
```bash
|
||||
curl -X POST {{API_URL}}/v1/track \
|
||||
-H "Authorization: Bearer pk_your_public_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "[email protected]",
|
||||
"event": "signed_up",
|
||||
"data": {
|
||||
"plan": "pro",
|
||||
"source": "landing"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Creates contact if doesn't exist, updates if does.
|
||||
|
||||
## Updating contacts
|
||||
|
||||
```bash
|
||||
curl -X PATCH {{API_URL}}/contacts/contact_id \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"data": {
|
||||
"plan": "premium",
|
||||
"mrr": 199
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
New fields added, existing fields overwritten, unmentioned fields preserved.
|
||||
|
||||
### Remove fields
|
||||
|
||||
Set to `null`:
|
||||
|
||||
```json
|
||||
{ "data": { "temporaryToken": null } }
|
||||
```
|
||||
|
||||
## Subscription status
|
||||
|
||||
**Subscribed (true):**
|
||||
- Receives marketing emails
|
||||
- Receives transactional emails
|
||||
|
||||
**Unsubscribed (false):**
|
||||
- Does NOT receive marketing emails
|
||||
- Still receives transactional emails
|
||||
|
||||
Template type controls this behavior. See [Template Types](/concepts/templates-types).
|
||||
|
||||
## Using contact data
|
||||
|
||||
### In templates
|
||||
|
||||
```html
|
||||
<h1>Hi {{firstName}}!</h1>
|
||||
<p>Your {{plan}} plan renews on {{renewalDate}}.</p>
|
||||
```
|
||||
|
||||
### In segments
|
||||
|
||||
Filter by data fields:
|
||||
- `plan equals "premium"`
|
||||
- `mrr greaterThan 100`
|
||||
- `loginCount lessThan 5`
|
||||
|
||||
### In workflows
|
||||
|
||||
```
|
||||
[Condition: plan equals "enterprise"]
|
||||
├─ True → [Send: Enterprise content]
|
||||
└─ False → [Send: Standard content]
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
**Consistent naming** — Use camelCase or snake_case, not both.
|
||||
|
||||
**Correct types** — Use `99` not `"99"` for numbers.
|
||||
|
||||
**ISO dates** — `"2024-03-15T10:30:00Z"` for date fields.
|
||||
|
||||
**Sync critical fields only** — Don't mirror entire database. Only fields used in emails, segments, or workflows.
|
||||
|
||||
**Update in real-time** — When user data changes, update contact immediately.
|
||||
|
||||
**Respect unsubscribes** — Never re-subscribe automatically.
|
||||
|
||||
## Deleting contacts
|
||||
|
||||
```bash
|
||||
curl -X DELETE {{API_URL}}/contacts/contact_id \
|
||||
-H "Authorization: Bearer sk_your_secret_key"
|
||||
```
|
||||
|
||||
Permanent deletion. Consider unsubscribing instead to preserve history.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Create segments](/concepts/segments-and-filters)
|
||||
- [Track events](/concepts/events-and-triggers)
|
||||
- [Use templates](/concepts/templates-types)
|
||||
Use for: one-time codes, tokens, session data.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
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": "[email protected]",
|
||||
"from": "[email protected]",
|
||||
"subject": "Order confirmed",
|
||||
"body": "<p>Your order has been confirmed.</p>"
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -1,180 +1,53 @@
|
||||
---
|
||||
title: Events and Triggers
|
||||
description: Track behavior and trigger workflows
|
||||
title: Events
|
||||
description: Track actions and trigger workflows
|
||||
icon: Activity
|
||||
---
|
||||
|
||||
## What are events
|
||||
## Overview
|
||||
|
||||
Events track user actions from your application. Use them to update contact data, trigger workflows, and build segments.
|
||||
Events track user actions. Use them to trigger workflows and update contact data.
|
||||
|
||||
## Tracking events
|
||||
|
||||
### Basic event
|
||||
## Tracking
|
||||
|
||||
```javascript
|
||||
await fetch('{{API_URL}}/v1/track', {
|
||||
await fetch('/v1/track', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
'Authorization': 'Bearer pk_your_public_key'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
event: 'signed_up',
|
||||
email: user.email
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Creates or updates contact. Event is recorded.
|
||||
|
||||
### Event with data
|
||||
|
||||
```javascript
|
||||
await fetch('{{API_URL}}/v1/track', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
event: 'purchase_completed',
|
||||
email: user.email,
|
||||
data: {
|
||||
plan: 'premium',
|
||||
mrr: 99
|
||||
}
|
||||
data: { plan: 'pro' }
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
Updates contact data fields. Data persists on contact record.
|
||||
Creates/updates contact and records the event.
|
||||
|
||||
## Public vs Secret keys
|
||||
## Keys
|
||||
|
||||
### Public Key (pk_*)
|
||||
- **Public key (pk_*):** Safe for client-side, only works with `/v1/track`
|
||||
- **Secret key (sk_*):** Server-side only, full API access
|
||||
|
||||
- Safe for client-side code
|
||||
- Only works with `/v1/track`
|
||||
- Use in browser/mobile apps
|
||||
## Event data
|
||||
|
||||
### Secret Key (sk_*)
|
||||
|
||||
- Server-side only
|
||||
- Works with all endpoints
|
||||
- Full API access
|
||||
|
||||
## Event naming
|
||||
|
||||
Use clear, past-tense verbs with underscores:
|
||||
|
||||
✅ `signed_up`, `purchase_completed`, `feature_activated`
|
||||
❌ `signup`, `buy`, `feature`
|
||||
|
||||
## Workflow triggers
|
||||
|
||||
### Event trigger
|
||||
|
||||
Workflow starts when event is tracked.
|
||||
|
||||
```
|
||||
Trigger: event = "signed_up"
|
||||
```
|
||||
|
||||
Track event:
|
||||
**Persistent (default):** Saved to contact
|
||||
```javascript
|
||||
track('signed_up', '[email protected]');
|
||||
data: { plan: 'premium' }
|
||||
```
|
||||
|
||||
Workflow starts for that contact.
|
||||
|
||||
### Wait for event
|
||||
|
||||
Workflow pauses until event occurs.
|
||||
|
||||
```
|
||||
[Wait for Event: purchase_completed, timeout: 7 days]
|
||||
├─ Event occurred → [Send: Thank you]
|
||||
└─ Timeout → [Send: Discount offer]
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
|
||||
### User lifecycle
|
||||
|
||||
**Non-persistent:** Available only to workflow
|
||||
```javascript
|
||||
// Signup
|
||||
track('signed_up', email, { source: 'landing' });
|
||||
|
||||
// First login
|
||||
track('first_login', email, { lastLoginAt: new Date().toISOString() });
|
||||
|
||||
// Subscription
|
||||
track('subscription_created', email, { plan: 'premium', mrr: 99 });
|
||||
data: {
|
||||
orderId: { value: 'order-123', persistent: false }
|
||||
}
|
||||
```
|
||||
|
||||
### E-commerce
|
||||
## Automatic events
|
||||
|
||||
```javascript
|
||||
// Cart abandonment
|
||||
track('cart_abandoned', email, {
|
||||
cartTotal: 149.99,
|
||||
cartUrl: `https://store.com/cart/${cartId}`
|
||||
});
|
||||
|
||||
// Purchase
|
||||
track('purchase_completed', email, {
|
||||
orderId: order.id,
|
||||
orderTotal: order.total
|
||||
});
|
||||
```
|
||||
|
||||
## Event-based segments
|
||||
|
||||
Track events to update contact data, then segment on that data.
|
||||
|
||||
1. Track login:
|
||||
```javascript
|
||||
track('login', email, {
|
||||
lastLoginAt: new Date().toISOString()
|
||||
});
|
||||
```
|
||||
|
||||
2. Create segment:
|
||||
```
|
||||
Field: lastLoginAt
|
||||
Operator: greaterThan
|
||||
Value: {{7_days_ago}}
|
||||
```
|
||||
|
||||
Contacts auto-join segment when they log in.
|
||||
|
||||
## Testing events
|
||||
|
||||
**Dashboard:** Contacts → Search email → Events tab
|
||||
|
||||
Shows all events for that contact.
|
||||
|
||||
**Workflows:** Check executions after tracking event to verify workflow triggered.
|
||||
|
||||
## Rate limits
|
||||
|
||||
- Public key: 100 requests/minute
|
||||
- Secret key: 1000 requests/minute
|
||||
|
||||
## Common issues
|
||||
|
||||
**Workflow not triggering:**
|
||||
- Workflow is enabled
|
||||
- Event name matches exactly (case-sensitive)
|
||||
- Contact email is correct
|
||||
|
||||
**Contact data not updating:**
|
||||
- Field names are case-sensitive
|
||||
- Values are correct type (number vs string)
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Build event-triggered workflows](/tutorials/welcome-series-workflow)
|
||||
- [Create event-based segments](/concepts/segments-and-filters)
|
||||
- [Track events from your app](/tutorials/event-tracking-integration)
|
||||
Plunk tracks these automatically:
|
||||
- `email.sent`, `email.opened`, `email.clicked`
|
||||
- `email.bounced`, `email.complained`
|
||||
- `segment.entered`, `segment.exited`
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
title: Core Concepts
|
||||
description: Understand how Plunk works
|
||||
icon: Lightbulb
|
||||
---
|
||||
|
||||
## Email methods
|
||||
|
||||
<Cards>
|
||||
<Card icon="GitCompare" title="Campaigns vs Workflows" href="/concepts/campaigns-vs-workflows">
|
||||
When to use each email method
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Data & contacts
|
||||
|
||||
<Cards>
|
||||
<Card icon="Users" title="Contacts and Data" href="/concepts/contacts-and-data">
|
||||
Store and manage your audience
|
||||
</Card>
|
||||
|
||||
<Card icon="Filter" title="Segments" href="/concepts/segments-and-filters">
|
||||
Create dynamic audience groups
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Sending emails
|
||||
|
||||
<Cards>
|
||||
<Card icon="Mail" title="Template Types" href="/concepts/templates-types">
|
||||
Marketing vs Transactional templates
|
||||
</Card>
|
||||
|
||||
<Card icon="Activity" title="Events and Triggers" href="/concepts/events-and-triggers">
|
||||
Track behavior and trigger workflows
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Performance & delivery
|
||||
|
||||
<Cards>
|
||||
<Card icon="Send" title="Email Deliverability" href="/concepts/email-deliverability">
|
||||
Reach the inbox
|
||||
</Card>
|
||||
|
||||
<Card icon="Zap" title="Scale and Performance" href="/concepts/scale-and-performance">
|
||||
Optimize for millions of contacts
|
||||
</Card>
|
||||
</Cards>
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"title": "Core Concepts",
|
||||
"pages": [
|
||||
"index",
|
||||
"contacts-and-data",
|
||||
"templates-types",
|
||||
"campaigns-vs-workflows",
|
||||
"segments-and-filters",
|
||||
"events-and-triggers",
|
||||
"email-deliverability",
|
||||
"scale-and-performance"
|
||||
"webhooks",
|
||||
"custom-domains",
|
||||
"attachments",
|
||||
"troubleshooting"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,150 +1,40 @@
|
||||
---
|
||||
title: Segments and Filters
|
||||
description: Create dynamic audience groups
|
||||
icon: Filter
|
||||
title: Segments
|
||||
description: Dynamic audience groups
|
||||
icon: Funnel
|
||||
---
|
||||
|
||||
## What are segments
|
||||
## Overview
|
||||
|
||||
Segments are dynamic groups of contacts based on data filters. They update automatically when contact data changes.
|
||||
Segments are dynamic groups based on filters. They update automatically when contact data changes.
|
||||
|
||||
Use segments to:
|
||||
- Target specific audiences in campaigns
|
||||
- Trigger workflows when contacts enter/exit
|
||||
- Filter contacts in dashboard
|
||||
## Creating
|
||||
|
||||
## Creating segments
|
||||
1. Go to **Segments** → **Create Segment**
|
||||
2. Name your segment
|
||||
3. Add filter conditions
|
||||
4. Save
|
||||
|
||||
### In dashboard
|
||||
## Operators
|
||||
|
||||
**Contacts** → **Segments** → **Create Segment**
|
||||
| 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` |
|
||||
|
||||
1. Name your segment
|
||||
2. Add filters
|
||||
3. Save
|
||||
## Combining filters
|
||||
|
||||
### Via API
|
||||
- **AND:** All conditions must match
|
||||
- **OR:** Any condition can match
|
||||
|
||||
```bash
|
||||
curl -X POST {{API_URL}}/segments \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Premium Users",
|
||||
"filters": [
|
||||
{
|
||||
"field": "plan",
|
||||
"operator": "equals",
|
||||
"value": "premium"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
## Membership tracking
|
||||
|
||||
## Filter operators
|
||||
Enable **Track membership changes** to:
|
||||
- Trigger workflows on segment entry/exit
|
||||
- Generate `segment.entered` and `segment.exited` events
|
||||
|
||||
| Operator | Use | Example |
|
||||
|----------|-----|---------|
|
||||
| `equals` | Exact match | `plan equals "pro"` |
|
||||
| `notEquals` | Not matching | `plan notEquals "free"` |
|
||||
| `contains` | Substring (case-insensitive) | `email contains "@company.com"` |
|
||||
| `greaterThan` | Number comparison | `mrr greaterThan 100` |
|
||||
| `lessThan` | Number comparison | `loginCount lessThan 5` |
|
||||
| `greaterThanOrEqual` | Inclusive comparison | `age greaterThanOrEqual 18` |
|
||||
| `lessThanOrEqual` | Inclusive comparison | `daysInactive lessThanOrEqual 30` |
|
||||
| `exists` | Field has value | `company exists` |
|
||||
| `notExists` | Field missing/null | `lastName notExists` |
|
||||
| `startsWith` | String prefix | `coupon startsWith "SAVE"` |
|
||||
| `endsWith` | String suffix | `email endsWith ".edu"` |
|
||||
|
||||
## Multiple filters (AND logic)
|
||||
|
||||
All filters must match.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Active Premium Users",
|
||||
"filters": [
|
||||
{ "field": "plan", "operator": "equals", "value": "premium" },
|
||||
{ "field": "loginCount", "operator": "greaterThan", "value": 5 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Contact matches only if: `plan = "premium"` AND `loginCount > 5`
|
||||
|
||||
## Dynamic updates
|
||||
|
||||
Segments update automatically.
|
||||
|
||||
**Example:**
|
||||
1. Segment: `plan equals "premium"`
|
||||
2. User upgrades: `plan` changes from "free" to "premium"
|
||||
3. Contact auto-added to segment
|
||||
|
||||
No manual refresh needed.
|
||||
|
||||
## Workflow triggers
|
||||
|
||||
Trigger workflows when contacts enter/exit segments.
|
||||
|
||||
### Enable tracking
|
||||
|
||||
```bash
|
||||
curl -X PATCH {{API_URL}}/segments/segment_id \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"trackMembership": true}'
|
||||
```
|
||||
|
||||
Only enable for segments used as triggers.
|
||||
|
||||
### Entry trigger
|
||||
|
||||
```
|
||||
[Trigger: contact enters "Premium Users"]
|
||||
↓
|
||||
[Send: Welcome to premium]
|
||||
```
|
||||
|
||||
### Exit trigger
|
||||
|
||||
```
|
||||
[Trigger: contact exits "Trial Users"]
|
||||
↓
|
||||
[Send: Trial ended offer]
|
||||
```
|
||||
|
||||
## Nested fields
|
||||
|
||||
Access nested data with dot notation.
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "preferences.newsletter",
|
||||
"operator": "equals",
|
||||
"value": true
|
||||
}
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
**Store correct types** — Use `99` not `"99"` for numbers. Use `true` not `"true"` for booleans.
|
||||
|
||||
**Track membership sparingly** — Only enable on segments used as workflow triggers.
|
||||
|
||||
**Name descriptively** — "High-Value Customers (MRR >$200)" not "Segment 3"
|
||||
|
||||
## Common issues
|
||||
|
||||
**Numeric filters not working** — Ensure field is stored as number, not string.
|
||||
|
||||
**Case-sensitive matching** — `equals "Pro"` doesn't match `plan = "pro"`. Use `contains` for case-insensitive.
|
||||
|
||||
**Missing field** — If field doesn't exist on contact, filter won't match.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Use segments in campaigns](/guides/campaigns)
|
||||
- [Trigger workflows on segment entry](/guides/workflows)
|
||||
- [Store contact data](/concepts/contacts-and-data)
|
||||
Only enable for segments used as workflow triggers.
|
||||
|
||||
@@ -1,112 +1,36 @@
|
||||
---
|
||||
title: Template Types
|
||||
description: Marketing vs Transactional templates
|
||||
description: Marketing vs Transactional
|
||||
icon: Mail
|
||||
---
|
||||
|
||||
## Two template types
|
||||
## Two types
|
||||
|
||||
| Template Type | Sends to Unsubscribed? | Use For |
|
||||
|--------------|------------------------|---------|
|
||||
| **Marketing** | No | Newsletters, promotions, announcements |
|
||||
| **Transactional** | Yes | Receipts, confirmations, password resets |
|
||||
| Type | Sends to Unsubscribed? | Use For |
|
||||
|------|------------------------|---------|
|
||||
| **Marketing** | No | Newsletters, promotions |
|
||||
| **Transactional** | Yes | Receipts, password resets |
|
||||
|
||||
## Marketing templates
|
||||
## Marketing
|
||||
|
||||
Only sends to subscribed contacts.
|
||||
Only sends to subscribed contacts. Automatically includes unsubscribe link.
|
||||
|
||||
**Use for:** Newsletters, product updates, promotional emails.
|
||||
## Transactional
|
||||
|
||||
**Behavior:**
|
||||
```javascript
|
||||
// Contact is unsubscribed
|
||||
POST /v1/send { to: "[email protected]", template: "newsletter" }
|
||||
// → Email NOT sent
|
||||
```
|
||||
Sends regardless of subscription status. Use only for essential emails.
|
||||
|
||||
## Transactional templates
|
||||
|
||||
Sends regardless of subscription status.
|
||||
|
||||
**Use for:** Order confirmations, password resets, account alerts.
|
||||
|
||||
**Behavior:**
|
||||
```javascript
|
||||
// Contact is unsubscribed
|
||||
POST /v1/send { to: "[email protected]", template: "receipt" }
|
||||
// → Email sent
|
||||
```
|
||||
|
||||
## Setting template type
|
||||
|
||||
### In dashboard
|
||||
|
||||
**Templates** → Create/Edit → **Type** dropdown
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
curl -X POST {{API_URL}}/templates \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Password Reset",
|
||||
"type": "TRANSACTIONAL",
|
||||
"subject": "Reset your password",
|
||||
"body": "<p>Click: {{resetLink}}</p>"
|
||||
}'
|
||||
```
|
||||
|
||||
Type: `MARKETING` or `TRANSACTIONAL`
|
||||
|
||||
## Changing type
|
||||
|
||||
Update anytime:
|
||||
|
||||
```bash
|
||||
curl -X PATCH {{API_URL}}/templates/template_id \
|
||||
-H "Authorization: Bearer sk_your_secret_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "MARKETING"}'
|
||||
```
|
||||
|
||||
**Warning:** Changing transactional → marketing stops sending to unsubscribed contacts.
|
||||
|
||||
## Unsubscribe links
|
||||
|
||||
Marketing templates auto-include unsubscribe link in footer.
|
||||
|
||||
Custom placement:
|
||||
```html
|
||||
<a href="{{unsubscribeUrl}}">Unsubscribe</a>
|
||||
```
|
||||
|
||||
Transactional templates don't need unsubscribe links.
|
||||
|
||||
## Choosing the right type
|
||||
## Choosing
|
||||
|
||||
Ask: "Would user be frustrated if they didn't receive this after unsubscribing?"
|
||||
|
||||
**If yes → Transactional**
|
||||
- Password resets
|
||||
- Order confirmations
|
||||
- Account alerts
|
||||
- **Yes → Transactional** (password resets, order confirmations)
|
||||
- **No → Marketing** (newsletters, promotions)
|
||||
|
||||
**If no → Marketing**
|
||||
- Newsletters
|
||||
- Product announcements
|
||||
- Promotions
|
||||
## Variables
|
||||
|
||||
## Best practices
|
||||
```html
|
||||
<h1>Hello {{firstName}}!</h1>
|
||||
<p>Your {{plan}} plan renews on {{renewalDate}}.</p>
|
||||
```
|
||||
|
||||
**Default to marketing** — Only use transactional for truly necessary emails.
|
||||
|
||||
**Don't abuse transactional** — Sending marketing content via transactional templates violates regulations and damages reputation.
|
||||
|
||||
**Test both states** — Verify behavior with subscribed and unsubscribed contacts.
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Create templates](/guides/templates)
|
||||
- [Manage subscriptions](/concepts/contacts-and-data)
|
||||
- [Send emails](/tutorials/first-transactional-email)
|
||||
**Fallback:** `{{firstName ?? 'there'}}`
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user