mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-03-22 06:11:27 -07:00
update skills
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user