Endpoints vínculos task ↔ Zoho Mail
functions/api/tasks/[id]/links/email.ts — añadido en s68 (commit 7617371, 2026-05-17).
Persiste en D1 referencias a mensajes de Zoho Mail vinculados a una task desde TaskModal. El mensaje real vive en Zoho Mail; esta tabla es el registro de vínculos local.
Endpoints
GET /api/tasks/{id}/links/email
Lista todos los emails vinculados a la task ordenados por linked_at DESC.
Respuesta 200:
[
{
"task_id": 42,
"zoho_message_id": "msg789",
"thread_id": "thread001",
"subject": "Oferta Rack 2U · Referencia XYZ",
"from_address": "proveedor@example.com",
"email_date": "2026-05-16T09:00:00Z",
"url": "https://mail.zoho.eu/zm/#mail/folder/inbox/message/msg789",
"linked_by": "Dani",
"linked_at": "2026-05-17T10:45:00"
}
]
POST /api/tasks/{id}/links/email
Vincula un mensaje de Zoho Mail a la task. Típicamente llamado desde el picker de emails en TaskZohoLinks.
Body requerido:
{
"zoho_message_id": "string",
"thread_id": "string (opcional)",
"subject": "string (opcional)",
"from_address": "string (opcional)",
"email_date": "ISO string (opcional)",
"url": "string (opcional)"
}
Autenticación: requiere CF Access. linked_by = TeamMember resuelto vía resolveTeamMember(request).
Conflicto: ON CONFLICT(task_id, zoho_message_id) DO UPDATE — actualiza metadatos del mensaje si ya vinculado.
Respuesta 201: el row insertado/actualizado.
Errores:
400—task_idinválido ozoho_message_idausente.403— actor no identificado como TeamMember.404— task no encontrada en D1.
DELETE /api/tasks/{id}/links/email?message_id={zoho_message_id}
Elimina el vínculo (no borra el mensaje en Zoho Mail).
Query param requerido: message_id.
Respuesta 200: { "deleted": 1 } (o 0 si no existía).
Tabla D1: task_emails
CREATE TABLE task_emails (
task_id INTEGER NOT NULL REFERENCES tasks(id),
zoho_message_id TEXT NOT NULL,
thread_id TEXT,
subject TEXT,
from_address TEXT,
email_date TEXT,
url TEXT,
linked_by TEXT NOT NULL, -- TeamMember
linked_at TEXT DEFAULT (datetime('now')),
PRIMARY KEY (task_id, zoho_message_id)
);
Creada en Migration 0027.
Integración con GET /api/tasks
El listado de tasks incluye email_count calculado en SQL:
(SELECT COUNT(*) FROM task_emails WHERE task_id = tasks.id) AS email_count
Permite badges en KanbanMini, TaskTableView, TaskCalendar sin round-trips adicionales.
Flujo típico desde TaskModal
Usuario clicka "Vincular email" en TaskZohoLinks
→ GET /api/zoho/mail/search?q=... (picker)
← [{ id, subject, from, date, url }, ...]
→ Usuario selecciona un mensaje
→ POST /api/tasks/{id}/links/email { zoho_message_id, subject, from_address, url, ... }
← 201 LinkRow
→ TaskZohoLinks re-fetchea GET /api/tasks/{id}/links/email
Para nuevo email (deep-link compose):
Usuario clicka "Nuevo email" en TaskZohoLinks
→ GET /api/zoho/mail/compose-url?subject=...&body=...
← { url: "https://mail.zoho.eu/zm/#mail/compose?..." }
→ window.open(url) (el usuario completa y envía desde Zoho)
El email enviado no se vincula automáticamente; el usuario puede vincularlo manualmente tras enviarlo.
Véase también
- [[entity—workers—endpoint—task-calendar-links]]
- [[entity—workers—service—zoho-lib]]
- [[entity—workers—service—staff-lib]]
- [[feature—zoho—per-user-oauth]]
- [[decision—20260517—integracion-zoho-calendar-mail-pivot]]