@kernhq/module-mail 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +662 -0
- package/dist/contract.d.ts +285 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +115 -0
- package/dist/contract.js.map +1 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +189 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/providers/index.d.ts +13 -0
- package/dist/server/providers/index.d.ts.map +1 -0
- package/dist/server/providers/index.js +36 -0
- package/dist/server/providers/index.js.map +1 -0
- package/dist/server/providers/mailgun.d.ts +9 -0
- package/dist/server/providers/mailgun.d.ts.map +1 -0
- package/dist/server/providers/mailgun.js +42 -0
- package/dist/server/providers/mailgun.js.map +1 -0
- package/dist/server/providers/postmark.d.ts +7 -0
- package/dist/server/providers/postmark.d.ts.map +1 -0
- package/dist/server/providers/postmark.js +38 -0
- package/dist/server/providers/postmark.js.map +1 -0
- package/dist/server/providers/resend.d.ts +7 -0
- package/dist/server/providers/resend.d.ts.map +1 -0
- package/dist/server/providers/resend.js +34 -0
- package/dist/server/providers/resend.js.map +1 -0
- package/dist/server/providers/ses.d.ts +9 -0
- package/dist/server/providers/ses.d.ts.map +1 -0
- package/dist/server/providers/ses.js +59 -0
- package/dist/server/providers/ses.js.map +1 -0
- package/dist/server/providers/smtp.d.ts +13 -0
- package/dist/server/providers/smtp.d.ts.map +1 -0
- package/dist/server/providers/smtp.js +36 -0
- package/dist/server/providers/smtp.js.map +1 -0
- package/dist/server/providers/types.d.ts +38 -0
- package/dist/server/providers/types.d.ts.map +1 -0
- package/dist/server/providers/types.js +2 -0
- package/dist/server/providers/types.js.map +1 -0
- package/dist/server/schema.d.ts +469 -0
- package/dist/server/schema.d.ts.map +1 -0
- package/dist/server/schema.js +40 -0
- package/dist/server/schema.js.map +1 -0
- package/dist/server/send.d.ts +32 -0
- package/dist/server/send.d.ts.map +1 -0
- package/dist/server/send.js +119 -0
- package/dist/server/send.js.map +1 -0
- package/dist/server/settings.d.ts +9 -0
- package/dist/server/settings.d.ts.map +1 -0
- package/dist/server/settings.js +35 -0
- package/dist/server/settings.js.map +1 -0
- package/dist/server/suppressions.d.ts +17 -0
- package/dist/server/suppressions.d.ts.map +1 -0
- package/dist/server/suppressions.js +46 -0
- package/dist/server/suppressions.js.map +1 -0
- package/dist/server/templates.d.ts +19 -0
- package/dist/server/templates.d.ts.map +1 -0
- package/dist/server/templates.js +55 -0
- package/dist/server/templates.js.map +1 -0
- package/migrations/0000_init.sql +39 -0
- package/migrations/0001_notes.sql +6 -0
- package/migrations/meta/0000_snapshot.json +288 -0
- package/migrations/meta/_journal.json +20 -0
- package/package.json +80 -0
- package/src/client/MailSettings.svelte +214 -0
- package/src/client/index.ts +29 -0
- package/templates/_layout.mjml +34 -0
- package/templates/invitation.mjml +16 -0
- package/templates/invitation.subject.txt +1 -0
- package/templates/invitation.txt +8 -0
- package/templates/magic-link.mjml +16 -0
- package/templates/magic-link.subject.txt +1 -0
- package/templates/magic-link.txt +6 -0
- package/templates/notification-digest.mjml +24 -0
- package/templates/notification-digest.subject.txt +1 -0
- package/templates/notification-digest.txt +8 -0
- package/templates/reset-password.mjml +16 -0
- package/templates/reset-password.subject.txt +1 -0
- package/templates/reset-password.txt +7 -0
- package/templates/test.mjml +12 -0
- package/templates/test.subject.txt +1 -0
- package/templates/test.txt +5 -0
- package/templates/verify-email.mjml +17 -0
- package/templates/verify-email.subject.txt +1 -0
- package/templates/verify-email.txt +7 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/server/send.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { core } from '@kernhq/contracts';
|
|
2
|
+
/** Replace secret fields with a placeholder before returning config to clients. */
|
|
3
|
+
export declare function maskConfig(config: core.MailProviderConfig): Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Merge a client-submitted config with the stored one: placeholder values keep
|
|
6
|
+
* the stored secret. Validates the result against the contracts schema.
|
|
7
|
+
*/
|
|
8
|
+
export declare function unmaskConfig(next: Record<string, unknown>, prev: core.MailProviderConfig | null): core.MailProviderConfig;
|
|
9
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAIxC,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKnF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,EAAE,IAAI,CAAC,kBAAkB,GAAG,IAAI,GACnC,IAAI,CAAC,kBAAkB,CAkBzB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { core } from '@kernhq/contracts';
|
|
2
|
+
import { KernError, SECRET_FIELD_NAMES } from '@kernhq/kernel';
|
|
3
|
+
import { SECRET_PLACEHOLDER } from '../contract.js';
|
|
4
|
+
/** Replace secret fields with a placeholder before returning config to clients. */
|
|
5
|
+
export function maskConfig(config) {
|
|
6
|
+
const out = { ...config };
|
|
7
|
+
for (const key of SECRET_FIELD_NAMES)
|
|
8
|
+
if (typeof out[key] === 'string' && out[key] !== '')
|
|
9
|
+
out[key] = SECRET_PLACEHOLDER;
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Merge a client-submitted config with the stored one: placeholder values keep
|
|
14
|
+
* the stored secret. Validates the result against the contracts schema.
|
|
15
|
+
*/
|
|
16
|
+
export function unmaskConfig(next, prev) {
|
|
17
|
+
const merged = { ...next };
|
|
18
|
+
for (const key of SECRET_FIELD_NAMES) {
|
|
19
|
+
if (merged[key] !== SECRET_PLACEHOLDER)
|
|
20
|
+
continue;
|
|
21
|
+
const prevValue = prev && prev.provider === merged.provider
|
|
22
|
+
? prev[key]
|
|
23
|
+
: undefined;
|
|
24
|
+
if (typeof prevValue !== 'string')
|
|
25
|
+
throw new KernError('BAD_REQUEST', `Missing value for secret field "${key}"`);
|
|
26
|
+
merged[key] = prevValue;
|
|
27
|
+
}
|
|
28
|
+
const parsed = core.MailProviderConfig.safeParse(merged);
|
|
29
|
+
if (!parsed.success)
|
|
30
|
+
throw new KernError('VALIDATION', 'Invalid mail provider config', {
|
|
31
|
+
issues: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`),
|
|
32
|
+
});
|
|
33
|
+
return parsed.data;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAEnD,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,MAA+B;IACxD,MAAM,GAAG,GAA4B,EAAE,GAAG,MAAM,EAAE,CAAA;IAClD,KAAK,MAAM,GAAG,IAAI,kBAAkB;QAClC,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAA;IACpF,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAA6B,EAC7B,IAAoC;IAEpC,MAAM,MAAM,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAA;IACnD,KAAK,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACrC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,kBAAkB;YAAE,SAAQ;QAChD,MAAM,SAAS,GACb,IAAI,IAAK,IAAgC,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;YACpE,CAAC,CAAE,IAAgC,CAAC,GAAG,CAAC;YACxC,CAAC,CAAC,SAAS,CAAA;QACf,IAAI,OAAO,SAAS,KAAK,QAAQ;YAC/B,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,mCAAmC,GAAG,GAAG,CAAC,CAAA;QAC/E,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACzB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACxD,IAAI,CAAC,MAAM,CAAC,OAAO;QACjB,MAAM,IAAI,SAAS,CAAC,YAAY,EAAE,8BAA8B,EAAE;YAChE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;SAC5E,CAAC,CAAA;IACJ,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Kernel } from '@kernhq/kernel';
|
|
2
|
+
export interface SuppressionEntry {
|
|
3
|
+
workspaceId: string | null;
|
|
4
|
+
email: string;
|
|
5
|
+
reason: 'bounce' | 'complaint' | 'manual';
|
|
6
|
+
source?: string | null;
|
|
7
|
+
}
|
|
8
|
+
/** Split recipients into deliverable and suppressed. Pure — used by the send path and tests. */
|
|
9
|
+
export declare function filterSuppressed(recipients: string[], suppressed: ReadonlySet<string>): {
|
|
10
|
+
deliverable: string[];
|
|
11
|
+
suppressed: string[];
|
|
12
|
+
};
|
|
13
|
+
/** Addresses suppressed for this workspace (or instance-wide) among `emails`. */
|
|
14
|
+
export declare function loadSuppressed(kernel: Kernel, workspaceId: string | null, emails: string[]): Promise<Set<string>>;
|
|
15
|
+
/** Record a suppression (idempotent per workspace + email). */
|
|
16
|
+
export declare function addSuppression(kernel: Kernel, entry: SuppressionEntry): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=suppressions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppressions.d.ts","sourceRoot":"","sources":["../../src/server/suppressions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAK5C,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAA;IACzC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAC9B;IAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAIjD;AAED,iFAAiF;AACjF,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAiBtB;AAED,+DAA+D;AAC/D,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB3F"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { uuidv7 } from '@kernhq/kernel';
|
|
2
|
+
import { and, eq, inArray, isNull, or } from 'drizzle-orm';
|
|
3
|
+
import { suppressions } from './schema.js';
|
|
4
|
+
/** Split recipients into deliverable and suppressed. Pure — used by the send path and tests. */
|
|
5
|
+
export function filterSuppressed(recipients, suppressed) {
|
|
6
|
+
const out = { deliverable: [], suppressed: [] };
|
|
7
|
+
for (const r of recipients)
|
|
8
|
+
(suppressed.has(r.toLowerCase()) ? out.suppressed : out.deliverable).push(r);
|
|
9
|
+
return out;
|
|
10
|
+
}
|
|
11
|
+
/** Addresses suppressed for this workspace (or instance-wide) among `emails`. */
|
|
12
|
+
export async function loadSuppressed(kernel, workspaceId, emails) {
|
|
13
|
+
if (emails.length === 0)
|
|
14
|
+
return new Set();
|
|
15
|
+
const lower = emails.map((e) => e.toLowerCase());
|
|
16
|
+
const rows = await kernel.database.withWorkspace(null, (tx) => tx
|
|
17
|
+
.select({ email: suppressions.email })
|
|
18
|
+
.from(suppressions)
|
|
19
|
+
.where(and(inArray(suppressions.email, lower), workspaceId
|
|
20
|
+
? or(eq(suppressions.workspaceId, workspaceId), isNull(suppressions.workspaceId))
|
|
21
|
+
: isNull(suppressions.workspaceId))));
|
|
22
|
+
return new Set(rows.map((r) => r.email));
|
|
23
|
+
}
|
|
24
|
+
/** Record a suppression (idempotent per workspace + email). */
|
|
25
|
+
export async function addSuppression(kernel, entry) {
|
|
26
|
+
const email = entry.email.toLowerCase();
|
|
27
|
+
await kernel.database.withWorkspace(null, async (tx) => {
|
|
28
|
+
const existing = await tx
|
|
29
|
+
.select({ id: suppressions.id })
|
|
30
|
+
.from(suppressions)
|
|
31
|
+
.where(and(eq(suppressions.email, email), entry.workspaceId
|
|
32
|
+
? eq(suppressions.workspaceId, entry.workspaceId)
|
|
33
|
+
: isNull(suppressions.workspaceId)))
|
|
34
|
+
.limit(1);
|
|
35
|
+
if (existing.length > 0)
|
|
36
|
+
return;
|
|
37
|
+
await tx.insert(suppressions).values({
|
|
38
|
+
id: uuidv7(),
|
|
39
|
+
workspaceId: entry.workspaceId,
|
|
40
|
+
email,
|
|
41
|
+
reason: entry.reason,
|
|
42
|
+
source: entry.source ?? null,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=suppressions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppressions.js","sourceRoot":"","sources":["../../src/server/suppressions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RenderedTemplate {
|
|
2
|
+
subject: string;
|
|
3
|
+
html: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RenderOptions {
|
|
7
|
+
/** shown in the header and footer of every email */
|
|
8
|
+
instanceName?: string;
|
|
9
|
+
footer?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function templateNames(): string[];
|
|
12
|
+
export declare function hasTemplate(name: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Render a named template: eta interpolation into the MJML body fragment,
|
|
15
|
+
* wrapped in the shared paper layout, compiled to email HTML, plus the plain-text
|
|
16
|
+
* fallback and subject line.
|
|
17
|
+
*/
|
|
18
|
+
export declare function renderTemplate(name: string, data: Record<string, unknown>, opts?: RenderOptions): Promise<RenderedTemplate>;
|
|
19
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/server/templates.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAYD,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAKxC;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAgB3B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { KernError } from '@kernhq/kernel';
|
|
5
|
+
import { Eta } from 'eta';
|
|
6
|
+
import mjml2html from 'mjml';
|
|
7
|
+
const templatesDir = join(dirname(fileURLToPath(import.meta.url)), '../../templates');
|
|
8
|
+
/** HTML fragments: interpolations are HTML-escaped. Text/subject: raw. */
|
|
9
|
+
const etaHtml = new Eta({ autoEscape: true, autoTrim: false });
|
|
10
|
+
const etaText = new Eta({ autoEscape: false, autoTrim: false });
|
|
11
|
+
const files = new Map();
|
|
12
|
+
function file(name) {
|
|
13
|
+
let content = files.get(name);
|
|
14
|
+
if (content === undefined) {
|
|
15
|
+
content = readFileSync(join(templatesDir, name), 'utf8');
|
|
16
|
+
files.set(name, content);
|
|
17
|
+
}
|
|
18
|
+
return content;
|
|
19
|
+
}
|
|
20
|
+
export function templateNames() {
|
|
21
|
+
return readdirSync(templatesDir)
|
|
22
|
+
.filter((f) => f.endsWith('.mjml') && !f.startsWith('_'))
|
|
23
|
+
.map((f) => f.replace(/\.mjml$/, ''))
|
|
24
|
+
.sort();
|
|
25
|
+
}
|
|
26
|
+
export function hasTemplate(name) {
|
|
27
|
+
return /^[a-z0-9-]+$/.test(name) && templateNames().includes(name);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Render a named template: eta interpolation into the MJML body fragment,
|
|
31
|
+
* wrapped in the shared paper layout, compiled to email HTML, plus the plain-text
|
|
32
|
+
* fallback and subject line.
|
|
33
|
+
*/
|
|
34
|
+
export async function renderTemplate(name, data, opts = {}) {
|
|
35
|
+
if (!hasTemplate(name))
|
|
36
|
+
throw new KernError('NOT_FOUND', `Unknown mail template: ${name}`);
|
|
37
|
+
const it = { instanceName: opts.instanceName ?? 'Kern', ...data };
|
|
38
|
+
const body = etaHtml.renderString(file(`${name}.mjml`), it);
|
|
39
|
+
const footer = opts.footer ?? `Sent by ${escapeHtml(String(it.instanceName))} · an open-source Kern instance`;
|
|
40
|
+
const mjml = etaText.renderString(file('_layout.mjml'), {
|
|
41
|
+
__instanceName: escapeHtml(String(it.instanceName)),
|
|
42
|
+
__body: body,
|
|
43
|
+
__footer: footer,
|
|
44
|
+
});
|
|
45
|
+
const { html, errors } = await mjml2html(mjml, { validationLevel: 'soft' });
|
|
46
|
+
if (!html)
|
|
47
|
+
throw new KernError('INTERNAL', `Template ${name} failed to compile: ${errors[0]?.message}`);
|
|
48
|
+
const text = etaText.renderString(file(`${name}.txt`), it).trim();
|
|
49
|
+
const subject = etaText.renderString(file(`${name}.subject.txt`), it).trim();
|
|
50
|
+
return { subject, html, text };
|
|
51
|
+
}
|
|
52
|
+
function escapeHtml(s) {
|
|
53
|
+
return s.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/server/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
CREATE SCHEMA IF NOT EXISTS "mod_mail";
|
|
2
|
+
--> statement-breakpoint
|
|
3
|
+
CREATE TABLE "mod_mail"."deliveries" (
|
|
4
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
5
|
+
"workspace_id" uuid,
|
|
6
|
+
"to" text[] NOT NULL,
|
|
7
|
+
"subject" text NOT NULL,
|
|
8
|
+
"provider" text NOT NULL,
|
|
9
|
+
"template" text,
|
|
10
|
+
"status" text DEFAULT 'queued' NOT NULL,
|
|
11
|
+
"provider_message_id" text,
|
|
12
|
+
"error" text,
|
|
13
|
+
"tags" text[] DEFAULT '{}'::text[] NOT NULL,
|
|
14
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
15
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
16
|
+
);
|
|
17
|
+
--> statement-breakpoint
|
|
18
|
+
CREATE TABLE "mod_mail"."inbound_routes" (
|
|
19
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
20
|
+
"workspace_id" uuid NOT NULL,
|
|
21
|
+
"token" text NOT NULL,
|
|
22
|
+
"target" text NOT NULL,
|
|
23
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
24
|
+
CONSTRAINT "inbound_routes_token_unique" UNIQUE("token")
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE TABLE "mod_mail"."suppressions" (
|
|
28
|
+
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
|
|
29
|
+
"workspace_id" uuid,
|
|
30
|
+
"email" text NOT NULL,
|
|
31
|
+
"reason" text NOT NULL,
|
|
32
|
+
"source" text,
|
|
33
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
34
|
+
);
|
|
35
|
+
--> statement-breakpoint
|
|
36
|
+
CREATE INDEX "deliveries_ws_idx" ON "mod_mail"."deliveries" USING btree ("workspace_id","created_at");--> statement-breakpoint
|
|
37
|
+
CREATE INDEX "deliveries_pmid_idx" ON "mod_mail"."deliveries" USING btree ("provider_message_id");--> statement-breakpoint
|
|
38
|
+
CREATE INDEX "inbound_routes_ws_idx" ON "mod_mail"."inbound_routes" USING btree ("workspace_id");--> statement-breakpoint
|
|
39
|
+
CREATE INDEX "suppressions_email_idx" ON "mod_mail"."suppressions" USING btree ("email","workspace_id");
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Mail tables are intentionally not row-level secured, in line with the global tables in mod_core.
|
|
2
|
+
-- `deliveries` and `suppressions` are operational records whose workspace_id is nullable: instance-level
|
|
3
|
+
-- mail (account emails sent before a workspace exists, and instance-wide suppressions) has no tenant to
|
|
4
|
+
-- scope to, and the send path writes outside any workspace transaction. Tenant access goes exclusively
|
|
5
|
+
-- through this module's API, which filters by workspace_id and requires mail.deliveries.view /
|
|
6
|
+
-- mail.settings.manage.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "5a8c7d39-7f1b-4fc5-ab12-ee84ad38fe94",
|
|
3
|
+
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
4
|
+
"version": "7",
|
|
5
|
+
"dialect": "postgresql",
|
|
6
|
+
"tables": {
|
|
7
|
+
"mod_mail.deliveries": {
|
|
8
|
+
"name": "deliveries",
|
|
9
|
+
"schema": "mod_mail",
|
|
10
|
+
"columns": {
|
|
11
|
+
"id": {
|
|
12
|
+
"name": "id",
|
|
13
|
+
"type": "uuid",
|
|
14
|
+
"primaryKey": true,
|
|
15
|
+
"notNull": true,
|
|
16
|
+
"default": "uuidv7()"
|
|
17
|
+
},
|
|
18
|
+
"workspace_id": {
|
|
19
|
+
"name": "workspace_id",
|
|
20
|
+
"type": "uuid",
|
|
21
|
+
"primaryKey": false,
|
|
22
|
+
"notNull": false
|
|
23
|
+
},
|
|
24
|
+
"to": {
|
|
25
|
+
"name": "to",
|
|
26
|
+
"type": "text[]",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true
|
|
29
|
+
},
|
|
30
|
+
"subject": {
|
|
31
|
+
"name": "subject",
|
|
32
|
+
"type": "text",
|
|
33
|
+
"primaryKey": false,
|
|
34
|
+
"notNull": true
|
|
35
|
+
},
|
|
36
|
+
"provider": {
|
|
37
|
+
"name": "provider",
|
|
38
|
+
"type": "text",
|
|
39
|
+
"primaryKey": false,
|
|
40
|
+
"notNull": true
|
|
41
|
+
},
|
|
42
|
+
"template": {
|
|
43
|
+
"name": "template",
|
|
44
|
+
"type": "text",
|
|
45
|
+
"primaryKey": false,
|
|
46
|
+
"notNull": false
|
|
47
|
+
},
|
|
48
|
+
"status": {
|
|
49
|
+
"name": "status",
|
|
50
|
+
"type": "text",
|
|
51
|
+
"primaryKey": false,
|
|
52
|
+
"notNull": true,
|
|
53
|
+
"default": "'queued'"
|
|
54
|
+
},
|
|
55
|
+
"provider_message_id": {
|
|
56
|
+
"name": "provider_message_id",
|
|
57
|
+
"type": "text",
|
|
58
|
+
"primaryKey": false,
|
|
59
|
+
"notNull": false
|
|
60
|
+
},
|
|
61
|
+
"error": {
|
|
62
|
+
"name": "error",
|
|
63
|
+
"type": "text",
|
|
64
|
+
"primaryKey": false,
|
|
65
|
+
"notNull": false
|
|
66
|
+
},
|
|
67
|
+
"tags": {
|
|
68
|
+
"name": "tags",
|
|
69
|
+
"type": "text[]",
|
|
70
|
+
"primaryKey": false,
|
|
71
|
+
"notNull": true,
|
|
72
|
+
"default": "'{}'::text[]"
|
|
73
|
+
},
|
|
74
|
+
"created_at": {
|
|
75
|
+
"name": "created_at",
|
|
76
|
+
"type": "timestamp with time zone",
|
|
77
|
+
"primaryKey": false,
|
|
78
|
+
"notNull": true,
|
|
79
|
+
"default": "now()"
|
|
80
|
+
},
|
|
81
|
+
"updated_at": {
|
|
82
|
+
"name": "updated_at",
|
|
83
|
+
"type": "timestamp with time zone",
|
|
84
|
+
"primaryKey": false,
|
|
85
|
+
"notNull": true,
|
|
86
|
+
"default": "now()"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"indexes": {
|
|
90
|
+
"deliveries_ws_idx": {
|
|
91
|
+
"name": "deliveries_ws_idx",
|
|
92
|
+
"columns": [
|
|
93
|
+
{
|
|
94
|
+
"expression": "workspace_id",
|
|
95
|
+
"isExpression": false,
|
|
96
|
+
"asc": true,
|
|
97
|
+
"nulls": "last"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"expression": "created_at",
|
|
101
|
+
"isExpression": false,
|
|
102
|
+
"asc": true,
|
|
103
|
+
"nulls": "last"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"isUnique": false,
|
|
107
|
+
"concurrently": false,
|
|
108
|
+
"method": "btree",
|
|
109
|
+
"with": {}
|
|
110
|
+
},
|
|
111
|
+
"deliveries_pmid_idx": {
|
|
112
|
+
"name": "deliveries_pmid_idx",
|
|
113
|
+
"columns": [
|
|
114
|
+
{
|
|
115
|
+
"expression": "provider_message_id",
|
|
116
|
+
"isExpression": false,
|
|
117
|
+
"asc": true,
|
|
118
|
+
"nulls": "last"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"isUnique": false,
|
|
122
|
+
"concurrently": false,
|
|
123
|
+
"method": "btree",
|
|
124
|
+
"with": {}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"foreignKeys": {},
|
|
128
|
+
"compositePrimaryKeys": {},
|
|
129
|
+
"uniqueConstraints": {},
|
|
130
|
+
"policies": {},
|
|
131
|
+
"checkConstraints": {},
|
|
132
|
+
"isRLSEnabled": false
|
|
133
|
+
},
|
|
134
|
+
"mod_mail.inbound_routes": {
|
|
135
|
+
"name": "inbound_routes",
|
|
136
|
+
"schema": "mod_mail",
|
|
137
|
+
"columns": {
|
|
138
|
+
"id": {
|
|
139
|
+
"name": "id",
|
|
140
|
+
"type": "uuid",
|
|
141
|
+
"primaryKey": true,
|
|
142
|
+
"notNull": true,
|
|
143
|
+
"default": "uuidv7()"
|
|
144
|
+
},
|
|
145
|
+
"workspace_id": {
|
|
146
|
+
"name": "workspace_id",
|
|
147
|
+
"type": "uuid",
|
|
148
|
+
"primaryKey": false,
|
|
149
|
+
"notNull": true
|
|
150
|
+
},
|
|
151
|
+
"token": {
|
|
152
|
+
"name": "token",
|
|
153
|
+
"type": "text",
|
|
154
|
+
"primaryKey": false,
|
|
155
|
+
"notNull": true
|
|
156
|
+
},
|
|
157
|
+
"target": {
|
|
158
|
+
"name": "target",
|
|
159
|
+
"type": "text",
|
|
160
|
+
"primaryKey": false,
|
|
161
|
+
"notNull": true
|
|
162
|
+
},
|
|
163
|
+
"created_at": {
|
|
164
|
+
"name": "created_at",
|
|
165
|
+
"type": "timestamp with time zone",
|
|
166
|
+
"primaryKey": false,
|
|
167
|
+
"notNull": true,
|
|
168
|
+
"default": "now()"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"indexes": {
|
|
172
|
+
"inbound_routes_ws_idx": {
|
|
173
|
+
"name": "inbound_routes_ws_idx",
|
|
174
|
+
"columns": [
|
|
175
|
+
{
|
|
176
|
+
"expression": "workspace_id",
|
|
177
|
+
"isExpression": false,
|
|
178
|
+
"asc": true,
|
|
179
|
+
"nulls": "last"
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"isUnique": false,
|
|
183
|
+
"concurrently": false,
|
|
184
|
+
"method": "btree",
|
|
185
|
+
"with": {}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"foreignKeys": {},
|
|
189
|
+
"compositePrimaryKeys": {},
|
|
190
|
+
"uniqueConstraints": {
|
|
191
|
+
"inbound_routes_token_unique": {
|
|
192
|
+
"name": "inbound_routes_token_unique",
|
|
193
|
+
"nullsNotDistinct": false,
|
|
194
|
+
"columns": ["token"]
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"policies": {},
|
|
198
|
+
"checkConstraints": {},
|
|
199
|
+
"isRLSEnabled": false
|
|
200
|
+
},
|
|
201
|
+
"mod_mail.suppressions": {
|
|
202
|
+
"name": "suppressions",
|
|
203
|
+
"schema": "mod_mail",
|
|
204
|
+
"columns": {
|
|
205
|
+
"id": {
|
|
206
|
+
"name": "id",
|
|
207
|
+
"type": "uuid",
|
|
208
|
+
"primaryKey": true,
|
|
209
|
+
"notNull": true,
|
|
210
|
+
"default": "uuidv7()"
|
|
211
|
+
},
|
|
212
|
+
"workspace_id": {
|
|
213
|
+
"name": "workspace_id",
|
|
214
|
+
"type": "uuid",
|
|
215
|
+
"primaryKey": false,
|
|
216
|
+
"notNull": false
|
|
217
|
+
},
|
|
218
|
+
"email": {
|
|
219
|
+
"name": "email",
|
|
220
|
+
"type": "text",
|
|
221
|
+
"primaryKey": false,
|
|
222
|
+
"notNull": true
|
|
223
|
+
},
|
|
224
|
+
"reason": {
|
|
225
|
+
"name": "reason",
|
|
226
|
+
"type": "text",
|
|
227
|
+
"primaryKey": false,
|
|
228
|
+
"notNull": true
|
|
229
|
+
},
|
|
230
|
+
"source": {
|
|
231
|
+
"name": "source",
|
|
232
|
+
"type": "text",
|
|
233
|
+
"primaryKey": false,
|
|
234
|
+
"notNull": false
|
|
235
|
+
},
|
|
236
|
+
"created_at": {
|
|
237
|
+
"name": "created_at",
|
|
238
|
+
"type": "timestamp with time zone",
|
|
239
|
+
"primaryKey": false,
|
|
240
|
+
"notNull": true,
|
|
241
|
+
"default": "now()"
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
"indexes": {
|
|
245
|
+
"suppressions_email_idx": {
|
|
246
|
+
"name": "suppressions_email_idx",
|
|
247
|
+
"columns": [
|
|
248
|
+
{
|
|
249
|
+
"expression": "email",
|
|
250
|
+
"isExpression": false,
|
|
251
|
+
"asc": true,
|
|
252
|
+
"nulls": "last"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"expression": "workspace_id",
|
|
256
|
+
"isExpression": false,
|
|
257
|
+
"asc": true,
|
|
258
|
+
"nulls": "last"
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"isUnique": false,
|
|
262
|
+
"concurrently": false,
|
|
263
|
+
"method": "btree",
|
|
264
|
+
"with": {}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
"foreignKeys": {},
|
|
268
|
+
"compositePrimaryKeys": {},
|
|
269
|
+
"uniqueConstraints": {},
|
|
270
|
+
"policies": {},
|
|
271
|
+
"checkConstraints": {},
|
|
272
|
+
"isRLSEnabled": false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"enums": {},
|
|
276
|
+
"schemas": {
|
|
277
|
+
"mod_mail": "mod_mail"
|
|
278
|
+
},
|
|
279
|
+
"sequences": {},
|
|
280
|
+
"roles": {},
|
|
281
|
+
"policies": {},
|
|
282
|
+
"views": {},
|
|
283
|
+
"_meta": {
|
|
284
|
+
"columns": {},
|
|
285
|
+
"schemas": {},
|
|
286
|
+
"tables": {}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "7",
|
|
3
|
+
"dialect": "postgresql",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"idx": 0,
|
|
7
|
+
"version": "7",
|
|
8
|
+
"when": 1787381458442,
|
|
9
|
+
"tag": "0000_init",
|
|
10
|
+
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "7",
|
|
15
|
+
"when": 1787381459442,
|
|
16
|
+
"tag": "0001_notes",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|