---
title: Troubleshooting
description: Common issues and solutions
---

## Authentication issues

### Invalid API key error

**Error:**
```json
{
  "code": 401,
  "error": "Unauthorized",
  "message": "Invalid API key"
}
```

**Solutions:**

1. **Verify key format** — Secret keys start with `sk_`, public keys start with `pk_`
2. **Check Authorization header** — Must use `Bearer` format: `Authorization: Bearer sk_your_secret_key`
3. **Ensure key hasn't been regenerated** — If you regenerated keys in dashboard, update your application
4. **Verify project access** — Key must belong to the project you're accessing

**Test your key:**
```bash
curl -X GET {{API_URL}}/contacts?limit=1 \
  -H "Authorization: Bearer sk_your_secret_key"
```

### Wrong key type for endpoint

**Error:**
```json
{
  "code": 401,
  "error": "Unauthorized",
  "message": "This endpoint requires a secret key (sk_*)"
}
```

**Solution:**

You're using a public key (`pk_*`) for an endpoint that requires a secret key.

- **Public keys** — Only work with `/v1/track` endpoint
- **Secret keys** — Required for all other endpoints

Use your secret key from **Settings > API Keys** in the dashboard.

## Email sending issues

### Emails not sending

**Check these common causes:**

1. **Billing limit reached** - Check your billing limits in Settings → Billing. If you hit your monthly limit, increase it or wait for monthly reset.

2. **Template not found**
```json
{
  "code": 404,
  "error": "Not Found",
  "message": "Template not found"
}
```

Verify template ID exists and belongs to your project.

3. **Contact unsubscribed**

Marketing templates skip unsubscribed contacts. Check contact subscription status:
```bash
curl -X GET {{API_URL}}/contacts/contact_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

If `subscribed: false`, use a transactional template for critical emails.

4. **Project disabled**

If your project is disabled, all email sends will fail. Contact support.

### Emails going to spam

**Common causes:**

1. **No custom domain** — Emails from default domain may have lower trust
2. **Low engagement** — Recipients not opening/clicking emails
3. **High bounce rate** — Too many invalid email addresses
4. **Spam complaints** — Recipients marking as spam

**Solutions:**

1. **Set up custom domain** — [Add your domain](/guides/custom-domains) for better deliverability
2. **Clean your list** — Remove bounced and inactive contacts
3. **Improve content** — Avoid spam trigger words, include clear unsubscribe link
4. **Warm up your domain** — Start with small volumes, gradually increase
5. **Segment your audience** — Only send relevant content to engaged users

### Tracking not working

**Open tracking:**

Requires:
- `trackingEnabled: true` on project
- Recipient email client loads images
- HTML email (not plain text)

Some email clients block tracking pixels. Open rates are estimates, not exact.

**Click tracking:**

Requires:
- Tracking enabled on project (check your project settings in dashboard)
- Links in email body (not subject)
- HTML email format

## Campaign issues

### Campaign won't send

**Error:**
```json
{
  "code": 400,
  "error": "Bad Request",
  "message": "Campaign must be in DRAFT or SCHEDULED status"
}
```

**Solution:**

Can only send campaigns with status `DRAFT`. If campaign is `SENT` or `CANCELLED`, duplicate it:

```bash
curl -X POST {{API_URL}}/campaigns/campaign_id/duplicate \
  -H "Authorization: Bearer sk_your_secret_key"
```

### No recipients for campaign

**Cause:**

Campaign targets a segment or filter with zero matching contacts.

**Solutions:**

1. **Check segment size**
```bash
curl -X GET {{API_URL}}/segments/segment_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

Look at `memberCount` field.

2. **Verify filters** — Test filters on segments page to see matches

3. **Check subscription status** — Marketing templates only send to subscribed contacts

### Scheduled campaign didn't send

**Check:**

1. **Verify schedule time** — Must be in future when scheduled
2. **Campaign status** — Should be `SCHEDULED`, not `CANCELLED`
3. **Project limits** — Billing limits may block sends

View campaign details:
```bash
curl -X GET {{API_URL}}/campaigns/campaign_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

## Workflow issues

### Workflow not triggering

**Common causes:**

1. **Workflow not enabled**

Check `enabled: true`:
```bash
curl -X GET {{API_URL}}/workflows/workflow_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

2. **Wrong event name**

Event names are case-sensitive. `user_signed_up` ≠ `User_Signed_Up`

Verify event name exactly matches workflow trigger.

3. **Contact already entered (allowReentry: false)**

If `allowReentry: false`, contact can only enter once. Check execution history:
```bash
curl -X GET {{API_URL}}/workflows/workflow_id/executions?contactId=contact_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

4. **Segment membership not tracked**

For `SEGMENT_ENTRY` triggers, segment must have `trackMembership: true`.

### Workflow emails not sending

**Check each email step execution:**

```bash
curl -X GET {{API_URL}}/workflows/workflow_id/executions/execution_id \
  -H "Authorization: Bearer sk_your_secret_key"
```

Look for step failures in response.

**Common causes:**

1. **Template not found** — Template was deleted
2. **Contact unsubscribed** — Marketing templates skip unsubscribed contacts
3. **Billing limit reached** — Monthly workflow email limit exceeded
4. **Workflow execution stopped** — Contact was deleted or execution was cancelled

### Workflow stuck on delay step

Delays are processed by background jobs. Check:

1. **scheduledFor** time — When should it execute?
2. **Current time** — Has the scheduled time passed?

Delays process every minute. Wait a few minutes and check again.

## Contact issues

### Contact not found

**Error:**
```json
{
  "code": 404,
  "error": "Not Found",
  "message": "Contact not found"
}
```

**Solutions:**

1. **Verify contact ID** — Check for typos in the ID
2. **Check project** — Contact may belong to different project
3. **Contact deleted** — Contact may have been deleted

Search by email instead:
```bash
curl -X GET "{{API_URL}}/contacts?search=user@example.com" \
  -H "Authorization: Bearer sk_your_secret_key"
```

### Duplicate contacts

Plunk automatically prevents duplicates. Creating a contact with existing email updates that contact instead of creating a new one.

If you see duplicates:
1. Check email addresses carefully (they may differ slightly)
2. Verify you're viewing the same project

### Contact data not updating

**Verify data format:**

```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"
    }
  }'
```

**Common issues:**

1. **Missing `data` wrapper** — Custom fields must be inside `data` object
2. **Wrong data types** — Numbers should be `99`, not `"99"`
3. **Nested too deeply** — Keep data relatively flat

## Segment issues

### Segment shows wrong count

Segment counts update every 5 minutes. Wait a few minutes and refresh.

For real-time count, query members directly:
```bash
curl -X GET "{{API_URL}}/segments/segment_id/contacts?limit=1" \
  -H "Authorization: Bearer sk_your_secret_key"
```

Check `total` field in response.

### Segment filter not working

**Test your filter:**

1. **Check field names** — Use `data.fieldName` for custom fields
2. **Verify operators** — `equals` for strings, `greaterThan` for numbers
3. **Match data types** — Don't compare string `"99"` with number `99`

**Example filters:**

```javascript
// ❌ Wrong
{ "field": "plan", "operator": "equals", "value": "pro" }

// ✓ Correct
{ "field": "data.plan", "operator": "equals", "value": "pro" }
```

### Segment entry/exit events not firing

Requires `trackMembership: true` on segment:

```bash
curl -X PATCH {{API_URL}}/segments/segment_id \
  -H "Authorization: Bearer sk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{"trackMembership": true}'
```

Membership is computed every 5 minutes. Events fire on the next computation cycle after a contact's segment membership changes.

## Domain verification issues

### Domain won't verify

**Common causes:**

1. **DNS not propagated** — Can take up to 48 hours
2. **Wrong DNS records** — Double-check DKIM tokens
3. **Conflicting records** — Remove old DKIM records from other email services

**Check DNS propagation:**
```bash
dig TXT _domainkey.yourdomain.com
```

Records should match the DKIM tokens provided by Plunk.

**Force verification check:**
```bash
curl -X POST {{API_URL}}/domains/domain_id/verify \
  -H "Authorization: Bearer sk_your_secret_key"
```

### Emails not sending from custom domain

1. **Verify domain is verified** — Check `verified: true` in domain settings
2. **Use correct `from` address** — Must be `@yourdomain.com`
3. **Check SPF and DKIM** — Ensure DNS records are correct

## Rate limiting

### 429 Too Many Requests

**Error:**
```json
{
  "code": 429,
  "error": "Too Many Requests",
  "message": "Rate limit exceeded"
}
```

**Solutions:**

1. **Implement exponential backoff** — Wait and retry with increasing delays
2. **Batch operations** — Group multiple operations when possible
3. **Spread requests** — Distribute load over time instead of bursts

**Example retry logic:**
```javascript
async function sendWithRetry(data, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch('{{API_URL}}/v1/send', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });

      if (response.status === 429) {
        const waitTime = Math.pow(2, i) * 1000; // Exponential backoff
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}
```

## Getting help

If you're still experiencing issues:

1. **Check API status** — Is there a known outage?
2. **Review error message** — Error messages usually indicate the problem
3. **Search documentation** — Look for specific error codes or messages
4. **Check GitHub issues** — Similar issues may already be reported
5. **Join Discord community** — Ask for help from other users
6. **Contact support** — Provide error details, request IDs, and steps to reproduce

**Include in support requests:**
- Error message and status code
- API endpoint and method
- Request body (remove sensitive data)
- Timestamp of the issue
- Project ID (if applicable)

