> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lettr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Complaints & Unsubscribes

> Handle spam complaints and unsubscribe requests in Lettr, with automatic suppression and webhook events that protect sender reputation.

Beyond bounces, spam complaints and unsubscribes are critical signals that affect your sender reputation. Handling these events properly is essential for maintaining deliverability and legal compliance.

## Spam Complaints

A spam complaint occurs when a recipient clicks "Report Spam" or "Mark as Junk" in their email client. This is one of the most damaging signals for your sender reputation.

### Understanding Complaints

When a recipient complains, their email provider sends a feedback report to Lettr. The address is automatically suppressed and you receive a `message.spam_complaint` webhook event.

```json theme={null}
[{
  "msys": {
    "message_event": {
      "type": "spam_complaint",
      "timestamp": "1705320000",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "recipient@example.com",
      "fbtype": "abuse",
      "friendly_from": "you@example.com",
      "subject": "Your Weekly Newsletter",
      "sending_ip": "192.168.1.1",
      "rcpt_meta": {
        "userId": "user_789"
      }
    }
  }
}]
```

| Field        | Type   | Description                            |
| ------------ | ------ | -------------------------------------- |
| `message_id` | string | Unique identifier for the email        |
| `rcpt_to`    | string | Recipient who complained               |
| `timestamp`  | string | Unix timestamp of the complaint        |
| `fbtype`     | string | Type of feedback (typically `abuse`)   |
| `rcpt_meta`  | object | Custom metadata from the original send |

### Complaint Rate Guidelines

| Complaint Rate | Status   | Action                        |
| -------------- | -------- | ----------------------------- |
| \< 0.1%        | Healthy  | Maintain current practices    |
| 0.1-0.3%       | Warning  | Review targeting and content  |
| > 0.3%         | Critical | Pause sending and investigate |

<Warning>
  A complaint rate above 0.1% is considered high by most email providers. Rates above 0.3% can result in your emails being blocked entirely.
</Warning>

### Handling Complaints

```javascript theme={null}
app.post('/webhooks/lettr', express.json(), async (req, res) => {
  res.sendStatus(200);

  for (const event of req.body) {
    const eventType = Object.keys(event.msys)[0];
    const data = event.msys[eventType];

    if (data.type === 'spam_complaint') {
      // Immediately suppress in your system
      await db.subscribers.update({
        where: { email: data.rcpt_to },
        data: {
          status: 'complained',
          canEmail: false,
          complainedAt: new Date(parseInt(data.timestamp) * 1000)
        }
      });

      // Log for analysis
      await logComplaint({
        email: data.rcpt_to,
        messageId: data.message_id,
        userId: data.rcpt_meta?.userId,
        timestamp: new Date(parseInt(data.timestamp) * 1000)
      });

      // Alert team - complaints are serious
      await alertTeam({
        type: 'spam_complaint',
        email: data.rcpt_to,
        messageId: data.message_id,
        severity: 'high'
      });
    }
  }
});
```

### Reducing Complaints

<AccordionGroup>
  <Accordion title="Only Email Subscribers">
    Never email people who haven't explicitly opted in to receive your emails. Purchased lists have extremely high complaint rates.
  </Accordion>

  <Accordion title="Set Clear Expectations">
    Tell subscribers what kind of emails they'll receive and how often. Unexpected emails lead to complaints.
  </Accordion>

  <Accordion title="Make Unsubscribing Easy">
    A visible unsubscribe link gives recipients an alternative to marking you as spam. Don't hide it.
  </Accordion>

  <Accordion title="Honor Frequency Preferences">
    If someone signs up for weekly updates, don't email them daily. Respect the frequency they agreed to.
  </Accordion>

  <Accordion title="Send Relevant Content">
    Irrelevant emails frustrate recipients. Segment your list and send targeted content.
  </Accordion>
</AccordionGroup>

## Unsubscribes

Unsubscribes occur when recipients opt out of receiving your emails. Lettr supports two unsubscribe methods, each with its own event type.

### List-Unsubscribe

Modern email clients show an "Unsubscribe" button in their interface. When clicked, this triggers a List-Unsubscribe request directly to Lettr, which generates an `unsubscribe.list_unsubscribe` event.

```json theme={null}
[{
  "msys": {
    "unsubscribe_event": {
      "type": "list_unsubscribe",
      "timestamp": "1705320000",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "recipient@example.com",
      "friendly_from": "you@example.com",
      "subject": "Your Weekly Newsletter",
      "rcpt_meta": {
        "userId": "user_789"
      }
    }
  }
}]
```

### Link Unsubscribe

When a recipient clicks an unsubscribe link in your email body, this triggers an `unsubscribe.link_unsubscribe` event.

```json theme={null}
[{
  "msys": {
    "unsubscribe_event": {
      "type": "link_unsubscribe",
      "timestamp": "1705320000",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "recipient@example.com",
      "friendly_from": "you@example.com",
      "subject": "Your Weekly Newsletter",
      "rcpt_meta": {
        "userId": "user_789"
      }
    }
  }
}]
```

| Field        | Type   | Description                              |
| ------------ | ------ | ---------------------------------------- |
| `message_id` | string | Unique identifier for the email          |
| `rcpt_to`    | string | Recipient who unsubscribed               |
| `timestamp`  | string | Unix timestamp of the unsubscribe        |
| `type`       | string | `list_unsubscribe` or `link_unsubscribe` |
| `rcpt_meta`  | object | Custom metadata from the original send   |

### Handling Unsubscribes

```javascript theme={null}
app.post('/webhooks/lettr', express.json(), async (req, res) => {
  res.sendStatus(200);

  for (const event of req.body) {
    const eventType = Object.keys(event.msys)[0];
    const data = event.msys[eventType];

    if (data.type === 'list_unsubscribe' || data.type === 'link_unsubscribe') {
      // Update subscription status
      await db.subscribers.update({
        where: { email: data.rcpt_to },
        data: {
          status: 'unsubscribed',
          canEmail: false,
          unsubscribedAt: new Date(parseInt(data.timestamp) * 1000),
          unsubscribeMethod: data.type
        }
      });

      // Log for analytics
      await analytics.track('unsubscribe', {
        email: data.rcpt_to,
        method: data.type,
        userId: data.rcpt_meta?.userId
      });
    }
  }
});
```

### Legal Requirements

<Note>
  Unsubscribe requests must be honored. Both CAN-SPAM (US) and GDPR (EU) require you to stop emailing someone who has unsubscribed. Violations can result in significant fines.
</Note>

**CAN-SPAM Requirements:**

* Process unsubscribe requests within 10 business days
* Don't charge a fee or require personal information to unsubscribe
* Don't transfer or sell the email address after unsubscribe

**GDPR Requirements:**

* Process unsubscribe requests without delay
* Provide clear and accessible unsubscribe mechanisms
* Maintain records of consent and withdrawal

## Managing Your Suppression Data

While Lettr automatically suppresses addresses that bounce, complain, or unsubscribe, you should also maintain your own records for several reasons:

### Why Maintain Your Own Records

1. **Prevent re-adding suppressed addresses**: When someone unsubscribes or complains, you shouldn't re-add them to your list
2. **Cross-platform consistency**: If you use multiple email providers, share suppression data between them
3. **Compliance auditing**: Maintain proof that you honored unsubscribe requests
4. **List hygiene**: Track patterns to improve your email program

### Building a Suppression System

```javascript theme={null}
// Example suppression record schema
const suppressionSchema = {
  email: String,           // The suppressed email address
  type: String,            // 'bounce', 'complaint', or 'unsubscribe'
  reason: String,          // Detailed reason (e.g., bounce_class or method)
  source: String,          // Where suppression originated
  messageId: String,       // Related message ID if applicable
  createdAt: Date,         // When suppression was added
  metadata: Object         // Additional context
};

// Check before sending
async function canSendTo(email) {
  const suppression = await db.suppressions.findOne({ email });
  return !suppression;
}

// Add suppression from webhook
async function addSuppression(data) {
  let type, reason;

  switch (data.type) {
    case 'bounce':
      const bounceClass = parseInt(data.bounce_class);
      if (![10, 30, 100].includes(bounceClass)) return; // Only suppress hard bounces
      type = 'bounce';
      reason = `bounce_class_${data.bounce_class}`;
      break;
    case 'spam_complaint':
      type = 'complaint';
      reason = data.fbtype || 'abuse';
      break;
    case 'list_unsubscribe':
    case 'link_unsubscribe':
      type = 'unsubscribe';
      reason = data.type;
      break;
    default:
      return;
  }

  await db.suppressions.upsert({
    where: { email: data.rcpt_to },
    create: {
      email: data.rcpt_to,
      type,
      reason,
      source: 'lettr_webhook',
      messageId: data.message_id,
      createdAt: new Date(),
      metadata: data.rcpt_meta
    },
    update: {
      type,
      reason,
      updatedAt: new Date()
    }
  });
}
```

### Pre-Send Validation

Before sending emails, validate addresses against your suppression list:

```javascript theme={null}
async function sendEmail(to, subject, html) {
  // Check suppression status
  const canSend = await canSendTo(to);

  if (!canSend) {
    console.log(`Skipping suppressed address: ${to}`);
    return { status: 'suppressed' };
  }

  // Proceed with sending
  const response = await fetch('https://app.lettr.com/api/emails', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      from: 'you@example.com',
      to: [to],
      subject,
      html
    })
  });

  return response.json();
}
```

## Webhook Event Summary

Subscribe to these events to track all suppression-related activity:

| Event Type                     | Trigger                    | Suppression |
| ------------------------------ | -------------------------- | ----------- |
| `message.bounce` (hard)        | Permanent delivery failure | Automatic   |
| `message.spam_complaint`       | Spam complaint             | Automatic   |
| `unsubscribe.list_unsubscribe` | List-Unsubscribe header    | Automatic   |
| `unsubscribe.link_unsubscribe` | Unsubscribe link click     | Automatic   |

Configure your webhook to receive these events:

```javascript theme={null}
// When creating a webhook, include these event types
const webhookEvents = [
  'message.bounce',
  'message.spam_complaint',
  'unsubscribe.list_unsubscribe',
  'unsubscribe.link_unsubscribe'
];
```

## Related Topics

<CardGroup cols={2}>
  <Card title="Bounces" icon="arrow-rotate-left" href="/learn/suppressions/bounces">
    Understanding and handling email bounces
  </Card>

  <Card title="Webhook Events" icon="bell" href="/learn/webhooks/event-types">
    Complete reference for all webhook events
  </Card>

  <Card title="Handling Webhooks" icon="code" href="/learn/webhooks/handling">
    Best practices for webhook implementation
  </Card>

  <Card title="Best Practices" icon="star" href="/learn/sending/best-practices">
    Tips for maintaining good deliverability
  </Card>
</CardGroup>
