Skip to content

Commit ed3b2b1

Browse files
committed
docs(api): add implementation plan for POST /v1/send endpoint
1 parent 12f1aac commit ed3b2b1

1 file changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# API Endpoint Implementation Plan: POST /v1/send
2+
3+
## 1. Przegląd punktu końcowego
4+
5+
Punkt końcowy `POST /v1/send` jest kluczowym elementem systemu Requil, odpowiedzialnym za procesowanie wysyłki wiadomości e-mail. Realizuje on proces walidacji requestu, weryfikacji idempotencji, sprawdzania limitów (Rate Limit), renderowania treści na podstawie szablonu oraz finalnej wysyłki.
6+
7+
Obsługuje dwa główne scenariusze użycia:
8+
1. **Wysyłka API (Server-to-Server)**: Autoryzacja przez API Key, używana przez integracje klientów.
9+
2. **Wysyłka Testowa (Dashboard)**: Autoryzacja przez sesję użytkownika (Supabase Auth), używana do testowania szablonów bezpośrednio z panelu administracyjnego.
10+
11+
## 2. Szczegóły żądania
12+
13+
- **Metoda HTTP**: `POST`
14+
- **Struktura URL**: `/v1/send`
15+
- **Autoryzacja**:
16+
- `Bearer {api_key}` (API Key ze scope `send`).
17+
- LUB Cookie Sesyjne (dla zapytań z Dashboardu - wysyłka testowa).
18+
- **Nagłówki**:
19+
- `Idempotency-Key` (Wymagany dla API Key): Unikalny klucz requestu. Opcjonalny dla wysyłki testowej z Dashboardu.
20+
- `Authorization`: `Bearer {api_key}` (opcjonalne, jeśli sesja).
21+
22+
- **Parametry Body (JSON)**:
23+
24+
| Pole | Typ | Wymagalność | Opis |
25+
|------|-----|-------------|------|
26+
| `template` | string | **Wymagane** | Alias lub ID szablonu do użycia. |
27+
| `to` | array | **Wymagane** | Lista odbiorców (min. 1). |
28+
| `to[].email` | string | **Wymagane** | Adres email odbiorcy. |
29+
| `to[].variables` | object | Opcjonalne | Zmienne do interpolacji dla konkretnego odbiorcy. |
30+
| `transport` | string | Opcjonalne | `resend` \| `smtp`. Domyślnie używa konfiguracji workspace lub fallback do internal. |
31+
| `subject` | string | Opcjonalne | Nadpisuje temat zdefiniowany w szablonie. |
32+
| `preheader` | string | Opcjonalne | Nadpisuje preheader z szablonu. |
33+
34+
## 3. Wykorzystywane typy
35+
36+
### DTO (Data Transfer Objects)
37+
38+
Zdefiniowane za pomocą Zod Schema w `modules/sending/sending.schema.ts` (do utworzenia):
39+
40+
```typescript
41+
// Uproszczony schemat
42+
const SendEmailSchema = z.object({
43+
template: z.string(),
44+
transport: z.enum(['resend', 'smtp']).optional(),
45+
subject: z.string().optional(),
46+
preheader: z.string().optional(),
47+
to: z.array(z.object({
48+
email: z.string().email(),
49+
variables: z.record(z.any()).optional()
50+
})).min(1).max(500) // Limit batcha
51+
});
52+
```
53+
54+
### Command Models
55+
56+
Command w architekturze CQRS (`modules/sending/commands/send-email/send-email.command.ts`):
57+
58+
```typescript
59+
export class SendEmailCommand {
60+
constructor(
61+
public readonly workspaceId: string,
62+
public readonly idempotencyKey: string,
63+
public readonly payload: SendEmailDto,
64+
public readonly traceId: string
65+
) {}
66+
}
67+
```
68+
69+
## 4. Szczegóły odpowiedzi
70+
71+
**Sukces (200 OK)** - Zwracany, gdy job został przyjęty i przetworzony (lub zakolejkowany).
72+
73+
```json
74+
{
75+
"ok": true,
76+
"job_id": "job_123...",
77+
"used_template_snapshot_id": "snap_456...",
78+
"sent": 1,
79+
"failed": 0,
80+
"warnings": []
81+
}
82+
```
83+
84+
**Błędy**:
85+
- `400 Bad Request`: Błąd walidacji danych (np. brak zmiennej).
86+
- `401 Unauthorized`: Nieprawidłowy API Key.
87+
- `409 Conflict`: Konflikt klucza idempotencji (użyty wcześniej z innym body) lub brak opublikowanego snapshotu.
88+
- `429 Too Many Requests`: Przekroczony limit zapytań dla workspace.
89+
- `500 Internal Server Error`: Błąd wewnętrzny.
90+
91+
## 5. Przepływ danych
92+
93+
1. **Request**: Klient (API lub Dashboard) wysyła `POST /v1/send`.
94+
2. **Auth Guard**:
95+
- **API Key**: Weryfikacja klucza, scope `send`, pobranie `workspace_id`.
96+
- **Session**: Weryfikacja sesji użytkownika (Supabase), sprawdzenie członkostwa w workspace (Header `x-workspace-id` lub context).
97+
3. **Idempotency Check (Redis)**:
98+
- Dla API Key: Wymagane sprawdzenie klucza `lock:send:{key}`.
99+
- Dla Dashboardu: Opcjonalne (można pominąć dla pojedynczych testów).
100+
4. **Rate Limit Check (Upstash)**: Weryfikacja limitu tokenów dla workspace.
101+
5. **Handler (CQRS)**:
102+
- Pobierz najnowszy snapshot szablonu (`template_snapshots`).
103+
- Walidacja zmiennych odbiorców względem schema snapshotu (AJV).
104+
- Utworzenie rekordu `SendJob` (status `pending`) oraz `SendRecipients` w DB.
105+
- **Renderowanie**: Wykorzystanie silnika **React Email**.
106+
- JSON Document (z DB) -> React Components -> HTML.
107+
- Interpolacja zmiennych.
108+
- Inicjalizacja transportu (Internal Resend lub Custom z DB).
109+
- Wysyłka via `@requil/transports`.
110+
- Aktualizacja statusu `SendJob` i `SendRecipients` (sent/failed).
111+
6. **Response**: Zwrócenie wyniku i zapisanie go w cache idempotencji.
112+
113+
## 6. Względy bezpieczeństwa
114+
115+
- **Dual Auth**: Obsługa zarówno API Key (S2S) jak i Sesji (Dashboard). Dashboard wymaga poprawnej konfiguracji CORS (whitelist domeny dashboardu) lub proxy.
116+
- **API Key Scopes**: Wymagany scope `send` dla kluczy API.
117+
- **Workspace Isolation**: `workspace_id` zawsze weryfikowany (z klucza lub uprawnień użytkownika).
118+
- **Input Validation**: Strict schema validation (Zod).
119+
- **HTML Sanitization**: React Email zapewnia escaping zmiennych, chroniąc przed XSS.
120+
- **CORS**: Endpoint musi być dostępny dla Dashboardu. Należy zweryfikować konfigurację w `apps/api/src/server/plugins/cors.ts` lub zezwolić na specyficzne originy dla tego route'a.
121+
122+
## 7. Obsługa błędów
123+
124+
Błędy są mapowane na odpowiednie kody HTTP przez globalny `ErrorHandler`:
125+
126+
- `TemplateNotFoundError` -> 404
127+
- `NoPublishedSnapshotError` -> 409
128+
- `IdempotencyConflictError` -> 409
129+
- `RateLimitExceededError` -> 429
130+
- `ValidationError` -> 400 (szczegóły w body)
131+
- `TransportError` -> 502/503 (jeśli retry nie pomogło)
132+
133+
## 8. Wydajność
134+
135+
- **Redis Cache**: Wyniki idempotencji i snapshoty szablonów cache'owane w Redis.
136+
- **Batch Insert**: `SendRecipients` zapisywane przy użyciu `db.insert().values([...])` dla wydajności.
137+
- **Connection Pooling**: Supabase pooler obsługuje połączenia DB.
138+
- **Future Optimization**: Dla dużych batchy (>500), handler powinien tylko kolejkować job (QStash) i zwracać 202 Accepted (post-MVP).
139+
140+
## 9. Etapy wdrożenia
141+
142+
### Krok 1: Scaffolding Modułu
143+
Utworzenie struktury katalogów w `apps/api/src/modules/sending`:
144+
- `sending.schema.ts` (Zod schema)
145+
- `commands/send-email/` (Route, Handler, Command)
146+
- `database/` (Repository port i implementacja Drizzle)
147+
- `domain/` (Model domenowy, błędy)
148+
149+
### Krok 2: Implementacja Warstwy Danych
150+
Implementacja `SendingRepository`:
151+
- Metoda `createJob(job: NewSendJob, recipients: NewSendRecipient[])`: Transakcyjny zapis joba i odbiorców.
152+
- Metoda `updateJobStatus(...)`.
153+
- Metoda `updateRecipientStatus(...)`.
154+
155+
### Krok 3: Implementacja Logiki Domenowej (Handler)
156+
Implementacja `SendEmailHandler`:
157+
- Integracja z `TemplateRepository` (pobranie snapshotu).
158+
- Logika walidacji zmiennych.
159+
- Logika renderowania (wykorzystanie **React Email** engine/komponentów).
160+
- Integracja z `@requil/transports`.
161+
162+
### Krok 4: Endpoint i Middleware
163+
Konfiguracja `send-email.route.ts`:
164+
- Rejestracja w Fastify.
165+
- Podpięcie walidacji Zod.
166+
- Konfiguracja **Auth Pre-handler**:
167+
- Sprawdź API Key.
168+
- Jeśli brak, sprawdź sesję (Supabase Auth).
169+
- Jeśli oba brak -> 401.
170+
- Obsługa nagłówka `Idempotency-Key` (warunkowa dla API Key).
171+
- Weryfikacja ustawień CORS dla endpointu (upewnienie się, że Dashboard ma dostęp).
172+
173+
### Krok 5: Testy
174+
- Unit testy dla Handlera (mockowanie repozytoriów, React Email i transportu).
175+
- Integration testy z bazą danych (Testcontainers).
176+
- Scenariusze testowe:
177+
- Wysyłka z poprawnym API Key.
178+
- Wysyłka testowa z sesji użytkownika.
179+
- Błąd walidacji.
180+
- Konflikt idempotencji.
181+

0 commit comments

Comments
 (0)