1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
-- Docket's own data, plus a cache of the mail it tracks. The mail server
-- stays the source of truth for messages and folders; JMAP import fills
-- these tables and fixtures seed them in dev.
-- Everyone Tailscale lets in, added on their first request.
CREATE TABLE users (
-- The Remote-User value that identifies this user.
login TEXT PRIMARY KEY,
-- The short name shown for them, from X-User-Slug; not unique.
slug TEXT NOT NULL
) STRICT;
CREATE TABLE accounts (
slug TEXT PRIMARY KEY,
name TEXT NOT NULL,
address TEXT NOT NULL,
read_only INTEGER NOT NULL CHECK (read_only IN (0, 1))
) STRICT;
-- Listed in insertion order, which is the order menus show them.
CREATE TABLE folders (
name TEXT PRIMARY KEY
) STRICT;
CREATE TABLE threads (
id INTEGER PRIMARY KEY,
account TEXT NOT NULL REFERENCES accounts (slug),
subject TEXT NOT NULL,
-- JMAP's threadId, cached: it can change on reimport.
jmap_thread_id TEXT
) STRICT;
-- Received messages carry state and folder; sent ones carry who sent them
-- and to whom. Each column belongs to one kind or the other.
CREATE TABLE messages (
id INTEGER PRIMARY KEY,
account TEXT NOT NULL REFERENCES accounts (slug),
-- The RFC 5322 Message-ID, without angle brackets.
message_id TEXT NOT NULL,
-- JMAP's Email id, cached: it can change on reimport.
jmap_id TEXT,
thread INTEGER NOT NULL REFERENCES threads (id),
at TEXT NOT NULL,
-- JSON arrays of display names or addresses.
cc TEXT NOT NULL DEFAULT '[]',
bcc TEXT NOT NULL DEFAULT '[]',
body TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('received', 'sent')),
from_name TEXT,
from_addr TEXT,
state TEXT CHECK (state IN ('inbox', 'do', 'wait', 'watch', 'done')),
folder TEXT REFERENCES folders (name),
sent_by TEXT REFERENCES users (login),
sent_to TEXT,
UNIQUE (account, message_id),
CHECK (
(kind = 'received'
AND from_name IS NOT NULL AND from_addr IS NOT NULL AND state IS NOT NULL
AND sent_by IS NULL AND sent_to IS NULL)
OR (kind = 'sent'
AND sent_by IS NOT NULL AND sent_to IS NOT NULL
AND from_name IS NULL AND from_addr IS NULL AND state IS NULL AND folder IS NULL)
)
) STRICT;
CREATE INDEX messages_thread ON messages (thread);
CREATE INDEX messages_state ON messages (state);
CREATE TABLE assignees (
message INTEGER NOT NULL REFERENCES messages (id),
user TEXT NOT NULL REFERENCES users (login),
PRIMARY KEY (message, user)
) STRICT;
-- Per-person read tracking; $seen is shared and can't say who read what.
CREATE TABLE reads (
user TEXT NOT NULL REFERENCES users (login),
message INTEGER NOT NULL REFERENCES messages (id),
PRIMARY KEY (user, message)
) STRICT;
CREATE TABLE comments (
id INTEGER PRIMARY KEY,
thread INTEGER NOT NULL REFERENCES threads (id),
author TEXT NOT NULL REFERENCES users (login),
at TEXT NOT NULL,
text TEXT NOT NULL
) STRICT;
CREATE INDEX comments_thread ON comments (thread);
-- Every change to a message's values, in the words of its undo toast.
CREATE TABLE history (
id INTEGER PRIMARY KEY,
message INTEGER NOT NULL REFERENCES messages (id),
user TEXT NOT NULL REFERENCES users (login),
at TEXT NOT NULL,
event TEXT NOT NULL
) STRICT;
CREATE INDEX history_message ON history (message);