# Gotchas & Troubleshooting

## Critical Pitfalls

### Stream Consumption (MOST COMMON)

**Problem:** "stream already consumed" or worker hangs

**Cause:** `message.raw` is `ReadableStream` - consume once only

**Solution:**

```typescript
// ❌ WRONG
const email1 = await parser.parse(await message.raw.arrayBuffer())
const email2 = await parser.parse(await message.raw.arrayBuffer()) // FAILS

// ✅ CORRECT
const raw = await message.raw.arrayBuffer()
const email = await parser.parse(raw)
```

Consume `message.raw` immediately before any async operations.

### Destination Verification

**Problem:** Emails not forwarding

**Cause:** Destination unverified

**Solution:** Add destination, check inbox for verification email, click link. Verify status: `GET /zones/{id}/email/routing/addresses`

### Mail Authentication

**Problem:** Legitimate emails rejected

**Cause:** Missing SPF/DKIM/DMARC on sender domain

**Solution:** Configure sender DNS:

```dns
example.com. IN TXT "v=spf1 include:_spf.example.com ~all"
selector._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=..."
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine"
```

### Envelope vs Header

**Problem:** Filtering on wrong address

**Solution:**

```typescript
// Routing/auth: envelope
if (message.from === 'trusted@example.com') {
}

// Display: headers
const display = message.headers.get('from')
```

### SendEmail Limits

| Issue       | Limit         | Solution                 |
| ----------- | ------------- | ------------------------ |
| From domain | Must own      | Use Email Routing domain |
| Volume      | ~100/min Free | Upgrade or throttle      |
| Attachments | Not supported | Link to R2               |
| Type        | Transactional | No bulk                  |

## Common Errors

### CPU Time Exceeded

**Cause:** Heavy parsing, large emails

**Solution:**

```typescript
const size = parseInt(message.headers.get('content-length') || '0') / 1024 / 1024
if (size > 20) {
  message.setReject('Too large')
  return
}

ctx.waitUntil(expensiveWork())
await message.forward('dest@example.com')
```

### Rule Not Triggering

**Causes:** Priority conflict, matcher error, catch-all override

**Solution:** Check priority (lower=first), verify exact match, confirm destination verified

### Undefined Property

**Cause:** Missing header

**Solution:**

```typescript
// ❌ WRONG
const subj = message.headers.get('subject').toLowerCase()

// ✅ CORRECT
const subj = message.headers.get('subject')?.toLowerCase() || ''
```

## Limits

| Resource     | Free     | Paid   |
| ------------ | -------- | ------ |
| Email size   | 25 MB    | 25 MB  |
| Rules        | 200      | 200    |
| Destinations | 200      | 200    |
| CPU time     | 10ms     | 50ms   |
| SendEmail    | ~100/min | Higher |

## Debugging

### Local

```bash
npx wrangler dev

curl -X POST 'http://localhost:8787/__email' \
  --header 'content-type: message/rfc822' \
  --data 'From: test@example.com
To: you@yourdomain.com
Subject: Test

Body'
```

### Production

```bash
npx wrangler tail
```

### Pattern

```typescript
export default {
  async email(message, env, ctx) {
    try {
      console.log('From:', message.from)
      await process(message, env)
    } catch (err) {
      console.error(err)
      message.setReject(err.message)
    }
  },
} satisfies ExportedHandler
```

## Auth Troubleshooting

### Check Status

```typescript
const auth = message.headers.get('authentication-results') || ''
console.log({
  spf: auth.includes('spf=pass'),
  dkim: auth.includes('dkim=pass'),
  dmarc: auth.includes('dmarc=pass'),
})

if (!auth.includes('pass')) {
  message.setReject('Failed auth')
  return
}
```

### SPF Issues

**Causes:** Forwarding breaks SPF, too many lookups (>10), missing includes

**Solution:**

```dns
; ✅ Good
example.com. IN TXT "v=spf1 include:_spf.google.com ~all"

; ❌ Bad - too many
example.com. IN TXT "v=spf1 include:a.com include:b.com ... ~all"
```

### DMARC Alignment

**Cause:** From domain must match SPF/DKIM domain

## Best Practices

1. Consume `message.raw` immediately
2. Verify destinations
3. Handle missing headers (`?.`)
4. Use envelope for routing
5. Check spam scores
6. Test locally first
7. Use `ctx.waitUntil` for background work
8. Size-check early
