# 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