Created comprehensive setup guide for syncing Skool community members to email lists via n8n. Includes browser token extraction guide, n8n workflow template, and configuration examples for multiple email platforms (ConvertKit, Mailchimp, ActiveCampaign, SendGrid). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
497 lines
12 KiB
Markdown
497 lines
12 KiB
Markdown
# Skool + n8n Configuration Reference
|
||
|
||
**VPS**: ai-termimal
|
||
**Created**: 2025-11-29
|
||
|
||
## Quick Configuration Checklist
|
||
|
||
Use this as a quick reference when setting up or updating your workflow.
|
||
|
||
### Required Values to Extract from Browser
|
||
|
||
| Value | Where to Find | Used In | Notes |
|
||
|-------|--------------|---------|-------|
|
||
| Cookie | Network > request-bulk-action > Headers > Request Headers | Nodes 2, 3, 4 | Very long string, expires periodically |
|
||
| User-Agent | Network > request-bulk-action > Headers > Request Headers | Nodes 2, 3, 4 | Your browser identifier |
|
||
| X-AWS-WAF-Token | Network > request-bulk-action > Headers > Request Headers | Nodes 2, 3 | Security token, expires |
|
||
| Request Payload | Network > request-bulk-action > Payload | Node 2 | JSON body for export request |
|
||
| Expires | Network > Doc > Downloaded file URL | Node 4 | Query parameter for file download |
|
||
| Signature | Network > Doc > Downloaded file URL | Node 4 | Query parameter for file download |
|
||
| Key-Pair-Id | Network > Doc > Downloaded file URL | Node 4 | Query parameter for file download |
|
||
|
||
---
|
||
|
||
## Customization Points
|
||
|
||
### 1. Schedule Frequency
|
||
|
||
**Location**: Schedule Trigger Node
|
||
**Default**: Every 2 hours
|
||
**Customization**:
|
||
```
|
||
Hours: 1, 2, 4, 6, 12, 24
|
||
Minutes: 15, 30, 45, 60
|
||
```
|
||
|
||
**Decision Guide**:
|
||
- **Active community** (>10 joins/day): Every 1-2 hours
|
||
- **Medium activity** (3-10 joins/day): Every 4-6 hours
|
||
- **Low activity** (<3 joins/day): Every 12-24 hours
|
||
|
||
### 2. Member Limit
|
||
|
||
**Location**: Limit Node
|
||
**Default**: 15 members
|
||
**Formula**: `(Expected joins/hour × Hours between triggers) × 1.5`
|
||
|
||
**Examples**:
|
||
- If you get 5 members/day and run every 2 hours: `(5/12 × 2) × 1.5 ≈ 2` → Set to 5 (minimum safe)
|
||
- If you get 50 members/day and run every 2 hours: `(50/12 × 2) × 1.5 ≈ 12` → Set to 15
|
||
- If you get 200 members/day and run every 2 hours: `(200/12 × 2) × 1.5 ≈ 50` → Set to 50
|
||
|
||
**Why 1.5x multiplier?**: Safety buffer for spikes in signups
|
||
|
||
### 3. Email Platform Integration
|
||
|
||
This is the most variable part of the workflow. Here are examples for popular platforms:
|
||
|
||
---
|
||
|
||
## Email Platform Examples
|
||
|
||
### ConvertKit
|
||
|
||
#### Search for Subscriber
|
||
```json
|
||
Node Type: HTTP Request
|
||
Method: GET
|
||
URL: https://api.convertkit.com/v3/subscribers
|
||
Query Parameters:
|
||
- api_secret: YOUR_API_SECRET
|
||
- email_address: {{ $json.email }}
|
||
|
||
Response: Returns subscriber if exists, empty if not
|
||
```
|
||
|
||
#### Create Subscriber
|
||
```json
|
||
Node Type: HTTP Request
|
||
Method: POST
|
||
URL: https://api.convertkit.com/v3/forms/YOUR_FORM_ID/subscribe
|
||
Headers:
|
||
- Content-Type: application/json
|
||
Body:
|
||
{
|
||
"api_key": "YOUR_API_KEY",
|
||
"email": "{{ $json.email }}",
|
||
"first_name": "{{ $json.first_name }}",
|
||
"tags": ["Skool Member"]
|
||
}
|
||
```
|
||
|
||
#### Tag Subscriber
|
||
```json
|
||
Node Type: HTTP Request
|
||
Method: POST
|
||
URL: https://api.convertkit.com/v3/tags/YOUR_TAG_ID/subscribe
|
||
Body:
|
||
{
|
||
"api_secret": "YOUR_API_SECRET",
|
||
"email": "{{ $json.email }}"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Mailchimp
|
||
|
||
#### Search for Subscriber
|
||
```json
|
||
Node Type: Mailchimp
|
||
Operation: Get Member
|
||
Audience ID: YOUR_AUDIENCE_ID
|
||
Email: {{ $json.email }}
|
||
|
||
If exists: Returns member info
|
||
If not exists: Returns error (handle with error workflow)
|
||
```
|
||
|
||
#### Create Subscriber
|
||
```json
|
||
Node Type: Mailchimp
|
||
Operation: Create or Update Member
|
||
Audience ID: YOUR_AUDIENCE_ID
|
||
Email: {{ $json.email }}
|
||
Status: subscribed
|
||
Merge Fields:
|
||
FNAME: {{ $json.first_name }}
|
||
LNAME: {{ $json.last_name }}
|
||
Tags: Skool Member, New Member
|
||
```
|
||
|
||
---
|
||
|
||
### ActiveCampaign
|
||
|
||
#### Search for Contact
|
||
```json
|
||
Node Type: ActiveCampaign
|
||
Operation: Get Contact
|
||
Email: {{ $json.email }}
|
||
```
|
||
|
||
#### Create Contact
|
||
```json
|
||
Node Type: ActiveCampaign
|
||
Operation: Create or Update Contact
|
||
Email: {{ $json.email }}
|
||
First Name: {{ $json.first_name }}
|
||
Last Name: {{ $json.last_name }}
|
||
Tags: Skool Member
|
||
Lists: YOUR_LIST_ID
|
||
```
|
||
|
||
---
|
||
|
||
### SendGrid
|
||
|
||
#### Search for Contact
|
||
```json
|
||
Node Type: HTTP Request
|
||
Method: POST
|
||
URL: https://api.sendgrid.com/v3/marketing/contacts/search
|
||
Headers:
|
||
- Authorization: Bearer YOUR_API_KEY
|
||
- Content-Type: application/json
|
||
Body:
|
||
{
|
||
"query": "email = '{{ $json.email }}'"
|
||
}
|
||
```
|
||
|
||
#### Create Contact
|
||
```json
|
||
Node Type: HTTP Request
|
||
Method: PUT
|
||
URL: https://api.sendgrid.com/v3/marketing/contacts
|
||
Headers:
|
||
- Authorization: Bearer YOUR_API_KEY
|
||
- Content-Type: application/json
|
||
Body:
|
||
{
|
||
"contacts": [
|
||
{
|
||
"email": "{{ $json.email }}",
|
||
"first_name": "{{ $json.first_name }}",
|
||
"last_name": "{{ $json.last_name }}"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Generic HTTP Request Template
|
||
|
||
For any email platform with API access:
|
||
|
||
#### Search Pattern
|
||
```json
|
||
Method: GET or POST
|
||
URL: [PLATFORM_API_URL]/search or /subscribers or /contacts
|
||
Authentication: API Key / Bearer Token (check platform docs)
|
||
Query/Body: { "email": "{{ $json.email }}" }
|
||
```
|
||
|
||
#### Create Pattern
|
||
```json
|
||
Method: POST or PUT
|
||
URL: [PLATFORM_API_URL]/create or /subscribe or /add
|
||
Authentication: API Key / Bearer Token
|
||
Body:
|
||
{
|
||
"email": "{{ $json.email }}",
|
||
"first_name": "{{ $json.first_name }}",
|
||
"last_name": "{{ $json.last_name }}",
|
||
"tags": ["Skool Member"],
|
||
"custom_fields": { ... }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## CSV Field Mapping
|
||
|
||
The CSV export from Skool typically contains these fields (may vary):
|
||
|
||
| Skool Field | Description | Use In Email Platform |
|
||
|-------------|-------------|---------------------|
|
||
| email | Member's email | Primary identifier |
|
||
| first_name | First name | Personalization |
|
||
| last_name | Last name | Personalization |
|
||
| joined_at | Join timestamp | Segmentation |
|
||
| status | active/inactive | Filter |
|
||
| profile_url | Skool profile link | Custom field |
|
||
| member_id | Unique ID | Reference |
|
||
|
||
**Access in n8n**: `{{ $json.field_name }}`
|
||
|
||
**Example**:
|
||
- Email: `{{ $json.email }}`
|
||
- Name: `{{ $json.first_name }} {{ $json.last_name }}`
|
||
- Join Date: `{{ $json.joined_at }}`
|
||
|
||
---
|
||
|
||
## Advanced Customizations
|
||
|
||
### 1. Filter by Join Date
|
||
|
||
Add a filter node after CSV extraction to only process members who joined in the last X hours:
|
||
|
||
```javascript
|
||
// In a Function node
|
||
const hoursAgo = 2; // Match your schedule frequency
|
||
const cutoffTime = new Date(Date.now() - (hoursAgo * 60 * 60 * 1000));
|
||
const joinedAt = new Date($json.joined_at);
|
||
|
||
return joinedAt > cutoffTime;
|
||
```
|
||
|
||
### 2. Add Custom Fields
|
||
|
||
Map Skool data to custom fields in your email platform:
|
||
|
||
```json
|
||
{
|
||
"email": "{{ $json.email }}",
|
||
"custom_fields": {
|
||
"skool_join_date": "{{ $json.joined_at }}",
|
||
"skool_profile": "{{ $json.profile_url }}",
|
||
"skool_member_id": "{{ $json.member_id }}",
|
||
"source": "Skool Community"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Conditional Tagging
|
||
|
||
Tag members based on Skool data:
|
||
|
||
```javascript
|
||
// In a Function node before adding to email platform
|
||
const tags = ['Skool Member'];
|
||
|
||
// Check if joined recently (within 24 hours)
|
||
const joinedRecently = (Date.now() - new Date($json.joined_at)) < 86400000;
|
||
if (joinedRecently) {
|
||
tags.push('New Member');
|
||
}
|
||
|
||
// Add to return object
|
||
return {
|
||
...$json,
|
||
tags: tags
|
||
};
|
||
```
|
||
|
||
### 4. Segment by Activity Level
|
||
|
||
If your Skool export includes activity data:
|
||
|
||
```javascript
|
||
// Assuming there's an activity_score field
|
||
if ($json.activity_score > 50) {
|
||
tags.push('Highly Engaged');
|
||
} else if ($json.activity_score > 20) {
|
||
tags.push('Moderately Engaged');
|
||
} else {
|
||
tags.push('Low Engagement');
|
||
}
|
||
```
|
||
|
||
### 5. Welcome Email Sequences
|
||
|
||
After adding to email platform, trigger different sequences:
|
||
|
||
```javascript
|
||
// Based on member type or interests
|
||
if ($json.interests && $json.interests.includes('AI')) {
|
||
sequenceId = 'AI_WELCOME_SEQUENCE';
|
||
} else if ($json.interests && $json.interests.includes('Marketing')) {
|
||
sequenceId = 'MARKETING_WELCOME_SEQUENCE';
|
||
} else {
|
||
sequenceId = 'GENERAL_WELCOME_SEQUENCE';
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Error Handling
|
||
|
||
### Recommended Error Workflow
|
||
|
||
Add an "Error Trigger" node to handle failures:
|
||
|
||
1. **Error Trigger Node**: Catches any workflow errors
|
||
2. **IF Node**: Check error type
|
||
3. **Notification Node**: Send alert (email, Slack, Discord)
|
||
|
||
**Common Errors**:
|
||
- `401 Unauthorized`: Tokens expired → Re-extract from browser
|
||
- `404 Not Found`: URL changed → Check Skool API structure
|
||
- `429 Too Many Requests`: Rate limited → Reduce frequency
|
||
- `CSV Parse Error`: Format changed → Check CSV structure
|
||
|
||
### Error Notification Template
|
||
|
||
```json
|
||
{
|
||
"subject": "Skool Sync Error",
|
||
"message": "Workflow failed at {{ $json.node }}\nError: {{ $json.error.message }}\nTime: {{ $now }}"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Performance Optimization
|
||
|
||
### Reduce n8n Executions
|
||
|
||
**Current Cost** (example):
|
||
- Runs every 2 hours = 12 executions/day
|
||
- Checks 15 members each = 180 checks/day
|
||
- With native integrations ≈ 200 operations/day
|
||
|
||
**Optimization**:
|
||
- Use native nodes when available (count as 1 operation vs HTTP = multiple)
|
||
- Increase schedule interval if acceptable latency
|
||
- Use conditional logic to skip unnecessary API calls
|
||
|
||
### Batch Processing
|
||
|
||
For high-volume communities, consider batching:
|
||
|
||
```javascript
|
||
// Instead of loop, batch add members
|
||
const newMembers = $input.all().map(item => ({
|
||
email: item.json.email,
|
||
first_name: item.json.first_name,
|
||
last_name: item.json.last_name
|
||
}));
|
||
|
||
// Send as array to email platform (if supported)
|
||
return [{ json: { contacts: newMembers } }];
|
||
```
|
||
|
||
---
|
||
|
||
## Security Best Practices
|
||
|
||
### 1. Use n8n Credentials
|
||
|
||
Don't hardcode tokens in nodes:
|
||
1. Go to **Credentials** in n8n
|
||
2. Create new credential for each service
|
||
3. Select credential in nodes instead of pasting values
|
||
|
||
### 2. Environment Variables
|
||
|
||
For self-hosted n8n, use environment variables:
|
||
```bash
|
||
export SKOOL_COOKIE="your_cookie"
|
||
export CONVERTKIT_API_KEY="your_key"
|
||
```
|
||
|
||
Access in workflow: `{{ $env.SKOOL_COOKIE }}`
|
||
|
||
### 3. Rotate Tokens Regularly
|
||
|
||
Set a reminder to refresh Skool tokens:
|
||
- Weekly for high-security requirements
|
||
- Bi-weekly for normal use
|
||
- When workflow fails (reactive)
|
||
|
||
### 4. Monitor for Unauthorized Access
|
||
|
||
Check your email platform for:
|
||
- Unusual subscription patterns
|
||
- Failed authentication attempts
|
||
- Unexpected data changes
|
||
|
||
---
|
||
|
||
## Testing Checklist
|
||
|
||
Before activating your workflow:
|
||
|
||
- [ ] Test Schedule Trigger manually
|
||
- [ ] Verify HTTP Request 1 returns `fileId`
|
||
- [ ] Verify HTTP Request 2 returns download URL
|
||
- [ ] Verify HTTP Request 3 downloads CSV
|
||
- [ ] Check CSV extraction produces member objects
|
||
- [ ] Confirm Limit node returns expected count
|
||
- [ ] Test email platform search (existing member)
|
||
- [ ] Test email platform create (new member)
|
||
- [ ] Verify tags are applied correctly
|
||
- [ ] Check welcome sequence triggers
|
||
- [ ] Test complete workflow end-to-end
|
||
- [ ] Monitor first scheduled execution
|
||
- [ ] Verify no duplicate additions after 24 hours
|
||
|
||
---
|
||
|
||
## Maintenance Schedule
|
||
|
||
### Weekly
|
||
- [ ] Check workflow execution history
|
||
- [ ] Verify no failed executions
|
||
- [ ] Spot-check email platform for new members
|
||
|
||
### Monthly
|
||
- [ ] Review member limit vs actual joins
|
||
- [ ] Optimize schedule frequency if needed
|
||
- [ ] Audit email platform for data accuracy
|
||
- [ ] Update tokens if experiencing auth issues
|
||
|
||
### Quarterly
|
||
- [ ] Review automation ROI (time saved)
|
||
- [ ] Consider additional automations
|
||
- [ ] Update documentation with learnings
|
||
|
||
---
|
||
|
||
## Troubleshooting Decision Tree
|
||
|
||
```
|
||
Workflow Failed?
|
||
├─ Authentication Error (401)
|
||
│ └─ Re-extract cookies and tokens
|
||
├─ No fileId returned
|
||
│ └─ Check request payload structure
|
||
├─ CSV download failed
|
||
│ └─ Re-extract download URL parameters
|
||
├─ No new members found
|
||
│ └─ Normal if no one joined (check Skool)
|
||
├─ Duplicates in email list
|
||
│ └─ Check IF node logic (should check existence)
|
||
└─ Email platform error
|
||
└─ Check API credentials and rate limits
|
||
```
|
||
|
||
---
|
||
|
||
## Support Resources
|
||
|
||
- **n8n Community**: https://community.n8n.io
|
||
- **n8n Documentation**: https://docs.n8n.io
|
||
- **Your Email Platform API Docs**: [Insert link]
|
||
- **Original Tutorial**: https://www.youtube.com/watch?v=vZaYYzb4y5Y
|
||
|
||
---
|
||
|
||
## Version History
|
||
|
||
- **v1.0** (2025-11-29): Initial configuration based on Sant Kala's tutorial
|
||
- Document updated for VPS: ai-termimal
|