Skip to content

Commit 58a893c

Browse files
committed
feat: email endpoint
1 parent 307facd commit 58a893c

File tree

5 files changed

+492
-307
lines changed

5 files changed

+492
-307
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Email Events API
2+
3+
## Environment Setup
4+
5+
Set the API key in your environment:
6+
7+
```bash
8+
EMAIL_API_KEY=your-secret-api-key-here
9+
```
10+
11+
## Data Validation & Schema
12+
13+
All email events are validated using Zod schemas with the following requirements:
14+
15+
### Schema Definition
16+
17+
```typescript
18+
{
19+
email_id: string (1-255 characters, required)
20+
domain: string (1-255 characters, required)
21+
labels: string[] (optional, max 100 chars per label, defaults to [])
22+
event_time: number (optional, Unix timestamp in milliseconds)
23+
received_at: number (optional, Unix timestamp in milliseconds)
24+
metadata: Record<string, unknown> (optional, defaults to {})
25+
}
26+
```
27+
28+
### Date Format Requirements
29+
30+
- **event_time**: Unix timestamp in **milliseconds** (e.g., `1703123456789`)
31+
- **received_at**: Unix timestamp in **milliseconds** (e.g., `1703123456789`)
32+
- If not provided, both default to the current server time
33+
- Use `Date.now()` in JavaScript or equivalent in other languages
34+
35+
### Validation Rules
36+
37+
- **email_id**: Must be 1-255 characters, will be hashed server-side for privacy
38+
- **domain**: Must be 1-255 characters (sender domain)
39+
- **labels**: Array of strings, each max 100 characters
40+
- **metadata**: Any JSON object for additional data
41+
- **Batch limit**: Maximum 100 events per batch request
42+
43+
## Single Email Event
44+
45+
**POST** `/email`
46+
47+
Headers:
48+
49+
```
50+
Content-Type: application/json
51+
X-API-Key: your-secret-api-key-here
52+
```
53+
54+
Body:
55+
56+
```json
57+
{
58+
"email_id": "unique-email-id-123",
59+
"domain": "sender-domain.com",
60+
"labels": ["inbox", "important"],
61+
"event_time": 1703123456789,
62+
"received_at": 1703123456789,
63+
"metadata": {
64+
"subject": "Meeting reminder",
65+
"from": "[email protected]",
66+
"custom_field": "value"
67+
}
68+
}
69+
```
70+
71+
Response:
72+
73+
```json
74+
{
75+
"status": "success",
76+
"type": "email",
77+
"event_id": "abc123..."
78+
}
79+
```
80+
81+
## Batch Email Events
82+
83+
**POST** `/email/batch`
84+
85+
Headers:
86+
87+
```
88+
Content-Type: application/json
89+
X-API-Key: your-secret-api-key-here
90+
```
91+
92+
Body:
93+
94+
```json
95+
[
96+
{
97+
"email_id": "email-1",
98+
"domain": "domain1.com",
99+
"labels": ["inbox"],
100+
"event_time": 1703123456789,
101+
"received_at": 1703123456789
102+
},
103+
{
104+
"email_id": "email-2",
105+
"domain": "domain2.com",
106+
"labels": ["spam"],
107+
"event_time": 1703123456790,
108+
"received_at": 1703123456790
109+
}
110+
]
111+
```
112+
113+
Response:
114+
115+
```json
116+
{
117+
"status": "success",
118+
"batch": true,
119+
"processed": 2,
120+
"results": [
121+
{
122+
"status": "success",
123+
"type": "email",
124+
"email_hash": "abc123..."
125+
},
126+
{
127+
"status": "success",
128+
"type": "email",
129+
"email_hash": "def456..."
130+
}
131+
]
132+
}
133+
```
134+
135+
## Required Fields
136+
137+
- `email_id`: Unique identifier for the email (will be hashed server-side)
138+
- `domain`: Sender domain
139+
140+
## Optional Fields
141+
142+
- `labels`: Array of strings for categorization
143+
- `event_time`: Timestamp when the label state happened (defaults to now)
144+
- `received_at`: Timestamp when email was first received (defaults to now)
145+
- `metadata`: JSON object for additional data
146+
147+
## Error Responses
148+
149+
### Missing API Key
150+
151+
```json
152+
{
153+
"status": "error",
154+
"message": "Invalid or missing API key"
155+
}
156+
```
157+
158+
### Schema Validation Error
159+
160+
```json
161+
{
162+
"status": "error",
163+
"message": "Invalid email event schema",
164+
"errors": [
165+
{
166+
"code": "too_small",
167+
"minimum": 1,
168+
"type": "string",
169+
"inclusive": true,
170+
"exact": false,
171+
"message": "String must contain at least 1 character(s)",
172+
"path": ["email_id"]
173+
}
174+
]
175+
}
176+
```
177+
178+
### Invalid Date Format
179+
180+
```json
181+
{
182+
"status": "error",
183+
"message": "Invalid email event schema",
184+
"errors": [
185+
{
186+
"code": "invalid_type",
187+
"expected": "number",
188+
"received": "string",
189+
"path": ["event_time"],
190+
"message": "Expected number, received string"
191+
}
192+
]
193+
}
194+
```
195+
196+
### Batch Schema Validation Error
197+
198+
```json
199+
{
200+
"status": "error",
201+
"message": "Invalid batch email event schema",
202+
"errors": [
203+
{
204+
"code": "too_big",
205+
"maximum": 100,
206+
"type": "array",
207+
"inclusive": true,
208+
"exact": false,
209+
"message": "Array must contain at most 100 element(s)",
210+
"path": []
211+
}
212+
]
213+
}
214+
```
215+
216+
### Duplicate Event
217+
218+
```json
219+
{
220+
"status": "success",
221+
"message": "Duplicate event ignored"
222+
}
223+
```
224+
225+
### JavaScript/Node.js
226+
227+
```javascript
228+
const emailEvent = {
229+
email_id: "email-123",
230+
domain: "example.com",
231+
labels: ["inbox", "important"],
232+
event_time: Date.now(), // Current timestamp in milliseconds
233+
received_at: Date.now(),
234+
metadata: { subject: "Meeting reminder" },
235+
};
236+
237+
const response = await fetch("http://basket.databuddy.cc/email", {
238+
method: "POST",
239+
headers: {
240+
"Content-Type": "application/json",
241+
"X-API-Key": process.env.EMAIL_API_KEY,
242+
},
243+
body: JSON.stringify(emailEvent),
244+
});
245+
```
246+
247+
## Notes
248+
249+
- **Privacy**: Email IDs are hashed with SHA-256 using domain as salt for privacy
250+
- **Deduplication**: Events are deduplicated using email hash + event time (7 day TTL)
251+
- **Rate Limits**: Batch endpoint supports up to 100 events per request
252+
- **Storage**: All data is stored in ClickHouse `analytics.email_events` table
253+
- **Timestamps**: Always use Unix timestamps in **milliseconds**, not seconds
254+
- **Authentication**: X-API-Key header is required for all requests

0 commit comments

Comments
 (0)