mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-22 06:11:27 -07:00
197 lines
4.0 KiB
Markdown
197 lines
4.0 KiB
Markdown
# 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
|