@benclmnt/postmock 0.0.1
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 +21 -0
- package/README.md +251 -0
- package/dist/seeds/conformance/00-core.d.ts +4 -0
- package/dist/seeds/conformance/00-core.js +12 -0
- package/dist/seeds/conformance/20-bounces.d.ts +3 -0
- package/dist/seeds/conformance/20-bounces.js +108 -0
- package/dist/seeds/conformance/40-messages.d.ts +3 -0
- package/dist/seeds/conformance/40-messages.js +144 -0
- package/dist/seeds/conformance/50-server-color.d.ts +8 -0
- package/dist/seeds/conformance/50-server-color.js +13 -0
- package/dist/seeds/conformance/70-account.d.ts +9 -0
- package/dist/seeds/conformance/70-account.js +29 -0
- package/dist/seeds/conformance/80-templates.d.ts +4 -0
- package/dist/seeds/conformance/80-templates.js +23 -0
- package/dist/seeds/conformance.d.ts +2 -0
- package/dist/seeds/conformance.js +3 -0
- package/dist/seeds/empty.d.ts +4 -0
- package/dist/seeds/empty.js +3 -0
- package/dist/seeds/lib/conformance.d.ts +16 -0
- package/dist/seeds/lib/conformance.js +18 -0
- package/dist/seeds/lib/history.d.ts +27 -0
- package/dist/seeds/lib/history.js +129 -0
- package/dist/seeds/lib/read-server.d.ts +10 -0
- package/dist/seeds/lib/read-server.js +10 -0
- package/dist/seeds/lib/template-server.d.ts +13 -0
- package/dist/seeds/lib/template-server.js +13 -0
- package/dist/src/api/account/authentication.d.ts +39 -0
- package/dist/src/api/account/authentication.js +97 -0
- package/dist/src/api/account/domains.d.ts +59 -0
- package/dist/src/api/account/domains.js +78 -0
- package/dist/src/api/account/paging.d.ts +12 -0
- package/dist/src/api/account/paging.js +35 -0
- package/dist/src/api/account/push.d.ts +14 -0
- package/dist/src/api/account/push.js +84 -0
- package/dist/src/api/account/routes.d.ts +1 -0
- package/dist/src/api/account/routes.js +288 -0
- package/dist/src/api/account/senders.d.ts +54 -0
- package/dist/src/api/account/senders.js +87 -0
- package/dist/src/api/account/servers.d.ts +40 -0
- package/dist/src/api/account/servers.js +148 -0
- package/dist/src/api/bounces/json.d.ts +25 -0
- package/dist/src/api/bounces/json.js +27 -0
- package/dist/src/api/bounces/routes.d.ts +1 -0
- package/dist/src/api/bounces/routes.js +127 -0
- package/dist/src/api/bulk/bulk.d.ts +43 -0
- package/dist/src/api/bulk/bulk.js +81 -0
- package/dist/src/api/bulk/routes.d.ts +1 -0
- package/dist/src/api/bulk/routes.js +252 -0
- package/dist/src/api/data-removals/routes.d.ts +1 -0
- package/dist/src/api/data-removals/routes.js +59 -0
- package/dist/src/api/email/json.d.ts +11 -0
- package/dist/src/api/email/json.js +33 -0
- package/dist/src/api/email/routes.d.ts +1 -0
- package/dist/src/api/email/routes.js +64 -0
- package/dist/src/api/inbound/routes.d.ts +1 -0
- package/dist/src/api/inbound/routes.js +40 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +3 -0
- package/dist/src/api/message-streams/routes.d.ts +1 -0
- package/dist/src/api/message-streams/routes.js +211 -0
- package/dist/src/api/message-streams/streams.d.ts +28 -0
- package/dist/src/api/message-streams/streams.js +50 -0
- package/dist/src/api/messages/json.d.ts +149 -0
- package/dist/src/api/messages/json.js +114 -0
- package/dist/src/api/messages/query.d.ts +19 -0
- package/dist/src/api/messages/query.js +64 -0
- package/dist/src/api/messages/routes.d.ts +1 -0
- package/dist/src/api/messages/routes.js +203 -0
- package/dist/src/api/server/json.d.ts +26 -0
- package/dist/src/api/server/json.js +25 -0
- package/dist/src/api/server/routes.d.ts +1 -0
- package/dist/src/api/server/routes.js +17 -0
- package/dist/src/api/stats/aggregate.d.ts +58 -0
- package/dist/src/api/stats/aggregate.js +66 -0
- package/dist/src/api/stats/routes.d.ts +1 -0
- package/dist/src/api/stats/routes.js +72 -0
- package/dist/src/api/suppressions/routes.d.ts +1 -0
- package/dist/src/api/suppressions/routes.js +98 -0
- package/dist/src/api/templates/content.d.ts +21 -0
- package/dist/src/api/templates/content.js +39 -0
- package/dist/src/api/templates/routes.d.ts +1 -0
- package/dist/src/api/templates/routes.js +164 -0
- package/dist/src/api/templates/send.d.ts +13 -0
- package/dist/src/api/templates/send.js +105 -0
- package/dist/src/api/templates/templates.d.ts +100 -0
- package/dist/src/api/templates/templates.js +218 -0
- package/dist/src/api/templates/validate.d.ts +15 -0
- package/dist/src/api/templates/validate.js +77 -0
- package/dist/src/api/triggers/routes.d.ts +1 -0
- package/dist/src/api/triggers/routes.js +62 -0
- package/dist/src/api/webhooks/routes.d.ts +14 -0
- package/dist/src/api/webhooks/routes.js +192 -0
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.js +95 -0
- package/dist/src/control/app.d.ts +5 -0
- package/dist/src/control/app.js +42 -0
- package/dist/src/control/endpoints/account.d.ts +1 -0
- package/dist/src/control/endpoints/account.js +68 -0
- package/dist/src/control/endpoints/bulk.d.ts +1 -0
- package/dist/src/control/endpoints/bulk.js +29 -0
- package/dist/src/control/endpoints/core.d.ts +1 -0
- package/dist/src/control/endpoints/core.js +104 -0
- package/dist/src/control/endpoints/inbound.d.ts +1 -0
- package/dist/src/control/endpoints/inbound.js +61 -0
- package/dist/src/control/endpoints/recipients.d.ts +1 -0
- package/dist/src/control/endpoints/recipients.js +136 -0
- package/dist/src/control/endpoints/smtp.d.ts +1 -0
- package/dist/src/control/endpoints/smtp.js +74 -0
- package/dist/src/control/endpoints/tracking.d.ts +1 -0
- package/dist/src/control/endpoints/tracking.js +95 -0
- package/dist/src/control/endpoints/webhooks.d.ts +1 -0
- package/dist/src/control/endpoints/webhooks.js +36 -0
- package/dist/src/control/index.d.ts +1 -0
- package/dist/src/control/index.js +3 -0
- package/dist/src/control/registry.d.ts +25 -0
- package/dist/src/control/registry.js +19 -0
- package/dist/src/control/seed.d.ts +11 -0
- package/dist/src/control/seed.js +32 -0
- package/dist/src/control/seeding.d.ts +10 -0
- package/dist/src/control/seeding.js +27 -0
- package/dist/src/discover.d.ts +16 -0
- package/dist/src/discover.js +49 -0
- package/dist/src/errors.d.ts +49 -0
- package/dist/src/errors.js +701 -0
- package/dist/src/events.d.ts +49 -0
- package/dist/src/events.js +19 -0
- package/dist/src/http/app.d.ts +8 -0
- package/dist/src/http/app.js +51 -0
- package/dist/src/http/auth.d.ts +7 -0
- package/dist/src/http/auth.js +32 -0
- package/dist/src/http/faults.d.ts +8 -0
- package/dist/src/http/faults.js +33 -0
- package/dist/src/http/normalize.d.ts +57 -0
- package/dist/src/http/normalize.js +225 -0
- package/dist/src/http/respond.d.ts +23 -0
- package/dist/src/http/respond.js +37 -0
- package/dist/src/http/routes.d.ts +60 -0
- package/dist/src/http/routes.js +64 -0
- package/dist/src/inbound/deliver.d.ts +9 -0
- package/dist/src/inbound/deliver.js +34 -0
- package/dist/src/inbound/parse.d.ts +21 -0
- package/dist/src/inbound/parse.js +71 -0
- package/dist/src/inbound/receive.d.ts +17 -0
- package/dist/src/inbound/receive.js +89 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +14 -0
- package/dist/src/mime/compose.d.ts +33 -0
- package/dist/src/mime/compose.js +58 -0
- package/dist/src/pipeline/addresses.d.ts +6 -0
- package/dist/src/pipeline/addresses.js +38 -0
- package/dist/src/pipeline/inactive.d.ts +8 -0
- package/dist/src/pipeline/inactive.js +20 -0
- package/dist/src/pipeline/submit.d.ts +138 -0
- package/dist/src/pipeline/submit.js +415 -0
- package/dist/src/plugins/message-events.d.ts +7 -0
- package/dist/src/plugins/message-events.js +76 -0
- package/dist/src/plugins/smtp.d.ts +15 -0
- package/dist/src/plugins/smtp.js +42 -0
- package/dist/src/plugins/stats.d.ts +7 -0
- package/dist/src/plugins/stats.js +78 -0
- package/dist/src/plugins/webhooks.d.ts +4 -0
- package/dist/src/plugins/webhooks.js +80 -0
- package/dist/src/plugins.d.ts +20 -0
- package/dist/src/plugins.js +9 -0
- package/dist/src/recipients/test-bounces.d.ts +15 -0
- package/dist/src/recipients/test-bounces.js +31 -0
- package/dist/src/recipients/transitions.d.ts +57 -0
- package/dist/src/recipients/transitions.js +279 -0
- package/dist/src/render/mustachio.d.ts +60 -0
- package/dist/src/render/mustachio.js +265 -0
- package/dist/src/runtime.d.ts +12 -0
- package/dist/src/runtime.js +11 -0
- package/dist/src/server.d.ts +25 -0
- package/dist/src/server.js +49 -0
- package/dist/src/smtp/auth.d.ts +35 -0
- package/dist/src/smtp/auth.js +66 -0
- package/dist/src/smtp/listener.d.ts +16 -0
- package/dist/src/smtp/listener.js +121 -0
- package/dist/src/smtp/mime.d.ts +36 -0
- package/dist/src/smtp/mime.js +181 -0
- package/dist/src/smtp/receive.d.ts +21 -0
- package/dist/src/smtp/receive.js +89 -0
- package/dist/src/smtp/reply.d.ts +14 -0
- package/dist/src/smtp/reply.js +24 -0
- package/dist/src/state/clock.d.ts +35 -0
- package/dist/src/state/clock.js +107 -0
- package/dist/src/state/ids.d.ts +6 -0
- package/dist/src/state/ids.js +7 -0
- package/dist/src/state/servers.d.ts +27 -0
- package/dist/src/state/servers.js +101 -0
- package/dist/src/state/store.d.ts +57 -0
- package/dist/src/state/store.js +98 -0
- package/dist/src/state/suppressions.d.ts +9 -0
- package/dist/src/state/suppressions.js +8 -0
- package/dist/src/state/types.d.ts +530 -0
- package/dist/src/state/types.js +41 -0
- package/dist/src/time.d.ts +11 -0
- package/dist/src/time.js +48 -0
- package/dist/src/tracking.d.ts +53 -0
- package/dist/src/tracking.js +150 -0
- package/dist/src/webhooks/deliver.d.ts +60 -0
- package/dist/src/webhooks/deliver.js +172 -0
- package/dist/src/webhooks/egress.d.ts +19 -0
- package/dist/src/webhooks/egress.js +40 -0
- package/dist/src/webhooks/outbound.d.ts +26 -0
- package/dist/src/webhooks/outbound.js +47 -0
- package/dist/src/webhooks/payloads.d.ts +111 -0
- package/dist/src/webhooks/payloads.js +128 -0
- package/package.json +55 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiError } from "../../errors.js";
|
|
3
|
+
import { absent, queryInt } from "../../http/normalize.js";
|
|
4
|
+
import { Unsupported } from "../../http/respond.js";
|
|
5
|
+
import { defineRoute } from "../../http/routes.js";
|
|
6
|
+
import { sendBatchWithTemplates, sendWithTemplate } from "./send.js";
|
|
7
|
+
import { activeTemplate, checkCanCreate, checkLayoutChange, checkTemplate, findTemplate, generatedAlias, parseOrReject, parseTemplateType, TEMPLATE_TYPES, templateJson, templateListJson, templateNotFound, templateSummaryJson, } from "./templates.js";
|
|
8
|
+
import { validateTemplate } from "./validate.js";
|
|
9
|
+
// Templates API (docs/06 §3). Template push (`PUT /templates/push`) belongs to the account API.
|
|
10
|
+
const text = absent(z.string());
|
|
11
|
+
// dotnet sends every key, null when unset (docs/08 §1.3). `LayoutTemplate: ""` clears the layout
|
|
12
|
+
// (refs/api_templates-api.md:545), so it keeps "" apart from null.
|
|
13
|
+
const templateBody = z.object({
|
|
14
|
+
Name: text,
|
|
15
|
+
Subject: text,
|
|
16
|
+
HtmlBody: text,
|
|
17
|
+
TextBody: text,
|
|
18
|
+
Alias: text,
|
|
19
|
+
TemplateType: text,
|
|
20
|
+
LayoutTemplate: z.string().nullable().optional(),
|
|
21
|
+
});
|
|
22
|
+
defineRoute({
|
|
23
|
+
method: "GET",
|
|
24
|
+
path: "/templates",
|
|
25
|
+
auth: "server",
|
|
26
|
+
handler: ({ store, query, auth }) => {
|
|
27
|
+
const paging = query.pick(z.object({ count: queryInt, offset: queryInt }));
|
|
28
|
+
// refs/api_templates-api.md:694-695: both required. Messages INFERRED.
|
|
29
|
+
if (!paging.success || paging.data.count < 1 || paging.data.offset < 0) {
|
|
30
|
+
throw apiError(1100, {
|
|
31
|
+
message: "The 'Count' and 'Offset' query parameters are required non-negative integers.",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const filter = query.get("TemplateType") ?? "All";
|
|
35
|
+
if (filter !== "All" && !TEMPLATE_TYPES.some((t) => t === filter)) {
|
|
36
|
+
throw apiError(1100, {
|
|
37
|
+
message: `Invalid 'TemplateType': '${filter}'. Use All, Standard or Layout.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const layout = query.get("LayoutTemplate");
|
|
41
|
+
// A deleted template leaves the list (sdk/postmark-dotnet/src/Postmark.Tests/ClientTemplateTests.cs:150-155).
|
|
42
|
+
const templates = [...store.state.templates.values()]
|
|
43
|
+
.filter((t) => t.ServerID === auth.server.ID &&
|
|
44
|
+
t.Active &&
|
|
45
|
+
(filter === "All" || t.TemplateType === filter) &&
|
|
46
|
+
(layout === undefined || t.LayoutTemplate === layout))
|
|
47
|
+
.sort((a, b) => a.TemplateId - b.TemplateId);
|
|
48
|
+
const { count, offset } = paging.data;
|
|
49
|
+
return {
|
|
50
|
+
TotalCount: templates.length,
|
|
51
|
+
Templates: templates.slice(offset, offset + count).map(templateListJson),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
// A deleted template stays readable with `Active: false`
|
|
56
|
+
// (sdk/postmark-dotnet/src/Postmark.Tests/ClientTemplateTests.cs:112-115).
|
|
57
|
+
defineRoute({
|
|
58
|
+
method: "GET",
|
|
59
|
+
path: "/templates/:idOrAlias",
|
|
60
|
+
auth: "server",
|
|
61
|
+
handler: ({ store, params, auth }) => {
|
|
62
|
+
const template = findTemplate(store.state, auth.server.ID, params.idOrAlias ?? "");
|
|
63
|
+
if (template === undefined)
|
|
64
|
+
throw templateNotFound(params.idOrAlias ?? "");
|
|
65
|
+
return templateJson(template);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
defineRoute({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: "/templates",
|
|
71
|
+
auth: "server",
|
|
72
|
+
handler: ({ store, body, auth }) => {
|
|
73
|
+
// An empty body is 1109 (refs/api_overview.md:86; INFERRED trigger).
|
|
74
|
+
if (body === undefined || JSON.stringify(body) === "{}") {
|
|
75
|
+
throw apiError(1109);
|
|
76
|
+
}
|
|
77
|
+
const input = parseOrReject(templateBody, body);
|
|
78
|
+
const { state } = store;
|
|
79
|
+
checkCanCreate(state, auth.server.ID);
|
|
80
|
+
const draft = {
|
|
81
|
+
TemplateId: 0,
|
|
82
|
+
ServerID: auth.server.ID,
|
|
83
|
+
Name: input.Name ?? "",
|
|
84
|
+
Alias: input.Alias ?? null,
|
|
85
|
+
Subject: input.Subject ?? null,
|
|
86
|
+
HtmlBody: input.HtmlBody ?? null,
|
|
87
|
+
TextBody: input.TextBody ?? null,
|
|
88
|
+
TemplateType: parseTemplateType(input.TemplateType),
|
|
89
|
+
LayoutTemplate: input.LayoutTemplate || null,
|
|
90
|
+
Active: true,
|
|
91
|
+
};
|
|
92
|
+
checkTemplate(state, draft);
|
|
93
|
+
const created = { ...draft, TemplateId: store.nextId("template") };
|
|
94
|
+
const template = {
|
|
95
|
+
...created,
|
|
96
|
+
Alias: created.Alias ?? generatedAlias(state, created),
|
|
97
|
+
};
|
|
98
|
+
state.templates.set(template.TemplateId, template);
|
|
99
|
+
return templateSummaryJson(template);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
// Edit changes only the fields sent: postmark.js sends `{Name}` alone, php sends only a layout
|
|
103
|
+
// (sdk/postmark.js/test/integration/Templates.test.ts:100; sdk/postmark-php/tests/PostmarkClientTemplatesTest.php:86).
|
|
104
|
+
defineRoute({
|
|
105
|
+
method: "PUT",
|
|
106
|
+
path: "/templates/:idOrAlias",
|
|
107
|
+
auth: "server",
|
|
108
|
+
handler: ({ store, body, params, auth }) => {
|
|
109
|
+
const input = parseOrReject(templateBody, body ?? {});
|
|
110
|
+
const { state } = store;
|
|
111
|
+
const before = activeTemplate(state, auth.server.ID, params.idOrAlias ?? "");
|
|
112
|
+
if (input.TemplateType !== undefined &&
|
|
113
|
+
parseTemplateType(input.TemplateType) !== before.TemplateType) {
|
|
114
|
+
// "After creation, it's not possible to change a template type." (refs/api_templates-api.md:544)
|
|
115
|
+
throw new Unsupported("the response to a TemplateType change is not captured");
|
|
116
|
+
}
|
|
117
|
+
const after = {
|
|
118
|
+
...before,
|
|
119
|
+
Name: input.Name ?? before.Name,
|
|
120
|
+
Alias: input.Alias ?? before.Alias,
|
|
121
|
+
Subject: input.Subject ?? before.Subject,
|
|
122
|
+
HtmlBody: input.HtmlBody ?? before.HtmlBody,
|
|
123
|
+
TextBody: input.TextBody ?? before.TextBody,
|
|
124
|
+
LayoutTemplate: input.LayoutTemplate === undefined || input.LayoutTemplate === null
|
|
125
|
+
? before.LayoutTemplate
|
|
126
|
+
: input.LayoutTemplate || null,
|
|
127
|
+
};
|
|
128
|
+
checkTemplate(state, after);
|
|
129
|
+
checkLayoutChange(state, before, after);
|
|
130
|
+
state.templates.set(after.TemplateId, after);
|
|
131
|
+
return templateSummaryJson(after);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
defineRoute({
|
|
135
|
+
method: "DELETE",
|
|
136
|
+
path: "/templates/:idOrAlias",
|
|
137
|
+
auth: "server",
|
|
138
|
+
handler: ({ store, params, auth }) => {
|
|
139
|
+
const { state } = store;
|
|
140
|
+
const template = activeTemplate(state, auth.server.ID, params.idOrAlias ?? "");
|
|
141
|
+
checkLayoutChange(state, template, null);
|
|
142
|
+
state.templates.set(template.TemplateId, { ...template, Active: false });
|
|
143
|
+
// refs/api_templates-api.md:769-771.
|
|
144
|
+
return { ErrorCode: 0, Message: `Template ${template.TemplateId} removed.` };
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
defineRoute({
|
|
148
|
+
method: "POST",
|
|
149
|
+
path: "/templates/validate",
|
|
150
|
+
auth: "server",
|
|
151
|
+
handler: ({ store, body, auth }) => validateTemplate(store.state, auth.server.ID, body ?? {}),
|
|
152
|
+
});
|
|
153
|
+
defineRoute({
|
|
154
|
+
method: "POST",
|
|
155
|
+
path: "/email/withTemplate",
|
|
156
|
+
auth: "server",
|
|
157
|
+
handler: (ctx) => sendWithTemplate(ctx, ctx.auth, ctx.body),
|
|
158
|
+
});
|
|
159
|
+
defineRoute({
|
|
160
|
+
method: "POST",
|
|
161
|
+
path: "/email/batchWithTemplates",
|
|
162
|
+
auth: "server",
|
|
163
|
+
handler: (ctx) => sendBatchWithTemplates(ctx, ctx.auth, ctx.body),
|
|
164
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RequestContext, ServerAuth } from "../../http/routes.ts";
|
|
2
|
+
export declare function sendWithTemplate(ctx: RequestContext, auth: ServerAuth, body: unknown): Promise<{
|
|
3
|
+
To: string;
|
|
4
|
+
SubmittedAt: string;
|
|
5
|
+
MessageID: string;
|
|
6
|
+
ErrorCode: number;
|
|
7
|
+
Message: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* One item per message in request order (docs/03 §2). Every item is validated before any is
|
|
11
|
+
* stored, as on `/email/batch`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sendBatchWithTemplates(ctx: RequestContext, auth: ServerAuth, body: unknown): Promise<import("../../errors.ts").ErrorBody[]>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiError, apiError } from "../../errors.js";
|
|
3
|
+
import { absent } from "../../http/normalize.js";
|
|
4
|
+
import { Unsupported } from "../../http/respond.js";
|
|
5
|
+
import { acceptOutbound, acceptOutbounds, draftFromJson, refuseSenderAfterDataError, validateOutbound, } from "../../pipeline/submit.js";
|
|
6
|
+
import { batchItem, sendResponse } from "../email/json.js";
|
|
7
|
+
import { renderContent } from "./content.js";
|
|
8
|
+
import { CONTENT_PARTS, parseOrReject, sendTemplate, templateIdField, templateModelField, } from "./templates.js";
|
|
9
|
+
// `/email/withTemplate` and `/email/batchWithTemplates` (docs/03 §1.4–§1.5, docs/06 §3.6).
|
|
10
|
+
const MAX_BATCH_MESSAGES = 500;
|
|
11
|
+
/** The template fields of a message; the send fields go through `draftFromJson`. */
|
|
12
|
+
const templateFields = z.object({
|
|
13
|
+
TemplateId: templateIdField,
|
|
14
|
+
TemplateAlias: absent(z.string()),
|
|
15
|
+
TemplateModel: templateModelField,
|
|
16
|
+
InlineCss: absent(z.boolean()),
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Renders one templated message and validates the result. Check order is INFERRED: templated vs
|
|
20
|
+
* content fields (1123), template (1101), model (1120), then the pipeline checks. A template error
|
|
21
|
+
* throws `ApiError`.
|
|
22
|
+
*/
|
|
23
|
+
function validateTemplated(ctx, auth, raw) {
|
|
24
|
+
const draft = draftFromJson(raw);
|
|
25
|
+
try {
|
|
26
|
+
return validateRendered(ctx, auth, raw, draft);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof ApiError)
|
|
30
|
+
refuseSenderAfterDataError(ctx, auth, draft.From);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function validateRendered(ctx, auth, raw, draft) {
|
|
35
|
+
const message = parseOrReject(templateFields, raw);
|
|
36
|
+
for (const part of CONTENT_PARTS) {
|
|
37
|
+
// R9: null and "" are absent (docs/08).
|
|
38
|
+
if (draft[part] !== undefined && draft[part] !== null && draft[part] !== "") {
|
|
39
|
+
throw apiError(1123, {
|
|
40
|
+
message: `The '${part}' field cannot be used when sending with a template.`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const { template, layout } = sendTemplate(ctx.store.state, auth.server.ID, message);
|
|
45
|
+
if (message.TemplateModel === undefined) {
|
|
46
|
+
throw apiError(1120, { message: "The 'TemplateModel' field is required." });
|
|
47
|
+
}
|
|
48
|
+
const rendered = renderContent(template, layout, message.TemplateModel, message.InlineCss ?? true);
|
|
49
|
+
if (!rendered.ok)
|
|
50
|
+
throw new Error(`stored template ${template.TemplateId} does not parse`);
|
|
51
|
+
return validateOutbound(ctx, {
|
|
52
|
+
auth,
|
|
53
|
+
channel: "rest",
|
|
54
|
+
draft: {
|
|
55
|
+
...draft,
|
|
56
|
+
Subject: rendered.content.Subject ?? undefined,
|
|
57
|
+
HtmlBody: rendered.content.HtmlBody ?? undefined,
|
|
58
|
+
TextBody: rendered.content.TextBody ?? undefined,
|
|
59
|
+
},
|
|
60
|
+
request: raw,
|
|
61
|
+
bulkRequestId: null,
|
|
62
|
+
templateId: template.TemplateId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
export async function sendWithTemplate(ctx, auth, body) {
|
|
67
|
+
if (!isObject(body))
|
|
68
|
+
throw new Unsupported("a send body that is not a JSON object");
|
|
69
|
+
const validation = validateTemplated(ctx, auth, body);
|
|
70
|
+
if (validation.outcome === "rejected")
|
|
71
|
+
return sendResponse(validation, "");
|
|
72
|
+
const { outbound } = validation;
|
|
73
|
+
return sendResponse(await acceptOutbound(ctx, outbound), outbound.draft.To);
|
|
74
|
+
}
|
|
75
|
+
const batchBody = z.object({ Messages: z.array(z.unknown()) });
|
|
76
|
+
/**
|
|
77
|
+
* One item per message in request order (docs/03 §2). Every item is validated before any is
|
|
78
|
+
* stored, as on `/email/batch`.
|
|
79
|
+
*/
|
|
80
|
+
export async function sendBatchWithTemplates(ctx, auth, body) {
|
|
81
|
+
const { Messages } = parseOrReject(batchBody, body);
|
|
82
|
+
if (!Messages.every(isObject))
|
|
83
|
+
throw new Unsupported("a batch message that is not an object");
|
|
84
|
+
if (Messages.length > MAX_BATCH_MESSAGES)
|
|
85
|
+
throw apiError(410);
|
|
86
|
+
const validations = Messages.map((raw) => {
|
|
87
|
+
try {
|
|
88
|
+
return validateTemplated(ctx, auth, raw);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (!(error instanceof ApiError))
|
|
92
|
+
throw error;
|
|
93
|
+
return { outcome: "rejected", field: "", error: error.body };
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
// Whether 1235 is per item or for the whole request is not captured (docs/03 §8 Q14).
|
|
97
|
+
if (validations.some((v) => v.outcome === "rejected" && v.error.ErrorCode === 1235)) {
|
|
98
|
+
throw new Unsupported("an unknown MessageStream in a batch (docs/03 §8 Q14)");
|
|
99
|
+
}
|
|
100
|
+
const accepted = await acceptOutbounds(ctx, validations.flatMap((v) => (v.outcome === "valid" ? [v.outbound] : [])));
|
|
101
|
+
let next = 0;
|
|
102
|
+
return validations.map((validation) => validation.outcome === "rejected"
|
|
103
|
+
? batchItem(validation, "")
|
|
104
|
+
: batchItem(accepted[next++], validation.outbound.draft.To));
|
|
105
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { State } from "../../state/store.ts";
|
|
3
|
+
import type { Template } from "../../state/types.ts";
|
|
4
|
+
export declare const TEMPLATE_TYPES: readonly ["Standard", "Layout"];
|
|
5
|
+
export type TemplateType = (typeof TEMPLATE_TYPES)[number];
|
|
6
|
+
export declare const CONTENT_PARTS: readonly ["Subject", "HtmlBody", "TextBody"];
|
|
7
|
+
export type ContentPart = (typeof CONTENT_PARTS)[number];
|
|
8
|
+
export type Content = Record<ContentPart, string | null>;
|
|
9
|
+
/** ErrorCode 403 for a field of the wrong type, as in the send pipeline (docs/02 §9 Q13, INFERRED). */
|
|
10
|
+
export declare const invalidField: (field: string) => import("../../errors.ts").ApiError;
|
|
11
|
+
/** Parses a body with zod; a failure is 403 naming the first bad field. */
|
|
12
|
+
export declare function parseOrReject<S extends z.ZodType>(schema: S, body: unknown): z.output<S>;
|
|
13
|
+
/** `Standard` (default) or `Layout` (refs/api_templates-api.md:544); another value is 1122 (INFERRED text). */
|
|
14
|
+
export declare function parseTemplateType(value: string | undefined): TemplateType;
|
|
15
|
+
/**
|
|
16
|
+
* The template a path or send names: digits are an ID (an alias starts with a letter), anything
|
|
17
|
+
* else an alias. An alias matches exactly, active templates first (INFERRED).
|
|
18
|
+
*/
|
|
19
|
+
export declare function findTemplate(state: State, serverId: number, idOrAlias: string): Template | undefined;
|
|
20
|
+
export declare const templateNotFound: (idOrAlias: string) => import("../../errors.ts").ApiError;
|
|
21
|
+
export declare function activeTemplate(state: State, serverId: number, idOrAlias: string): Template;
|
|
22
|
+
/** An active layout by alias; a missing or standard one is 1101 (refs/api_overview.md:84). */
|
|
23
|
+
export declare function activeLayout(state: State, serverId: number, alias: string): Template;
|
|
24
|
+
/**
|
|
25
|
+
* The placeholder rule for one part: a layout body holds `{{{ @content }}}` exactly once
|
|
26
|
+
* (refs/api_templates-api.md:541-542); a standard template holds none (INFERRED).
|
|
27
|
+
*/
|
|
28
|
+
export declare function layoutRuleError(type: TemplateType, part: ContentPart, placeholders: number): string | undefined;
|
|
29
|
+
/** A template before create: `Alias` is null when the request sets none. */
|
|
30
|
+
export type TemplateDraft = Omit<Template, "Alias"> & {
|
|
31
|
+
Alias: string | null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Checks a template as it will be stored, after create or edit. Error codes per docs/02 §4.4;
|
|
35
|
+
* every message is INFERRED except the 1131 prefix the postmark.js live test reads
|
|
36
|
+
* (sdk/postmark.js/test/integration/Templates.test.ts:122).
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkTemplate(state: State, template: TemplateDraft): void;
|
|
39
|
+
export declare function checkCanCreate(state: State, serverId: number): void;
|
|
40
|
+
/** A layout that templates use keeps its alias and cannot be deleted. */
|
|
41
|
+
export declare function checkLayoutChange(state: State, before: Template, after: Template | null): void;
|
|
42
|
+
/**
|
|
43
|
+
* A new template without an alias gets one. The dotnet and php live tests read a layout alias they
|
|
44
|
+
* never set (sdk/postmark-php/tests/PostmarkClientTemplatesTest.php:55-57). The postmark-cli pull
|
|
45
|
+
* test creates a standard template without an alias and expects every template pulled: pull stops
|
|
46
|
+
* at the first template without one (sdk/postmark-cli/test/integration/templates.pull.test.ts:47-50,
|
|
47
|
+
* sdk/postmark-cli/src/commands/templates/pull.ts:142-151). The format is INFERRED.
|
|
48
|
+
*/
|
|
49
|
+
export declare function generatedAlias(state: State, template: Omit<Template, "Alias">): string;
|
|
50
|
+
/** GET response, doc field order (refs/api_templates-api.md:457-468). */
|
|
51
|
+
export declare const templateJson: (t: Template) => {
|
|
52
|
+
Name: string;
|
|
53
|
+
TemplateId: number;
|
|
54
|
+
Subject: string | null;
|
|
55
|
+
HtmlBody: string | null;
|
|
56
|
+
TextBody: string | null;
|
|
57
|
+
AssociatedServerId: number;
|
|
58
|
+
Active: boolean;
|
|
59
|
+
Alias: string;
|
|
60
|
+
TemplateType: "Layout" | "Standard";
|
|
61
|
+
LayoutTemplate: string | null;
|
|
62
|
+
};
|
|
63
|
+
/** List item (refs/api_templates-api.md:712-731). */
|
|
64
|
+
export declare const templateListJson: (t: Template) => {
|
|
65
|
+
Active: boolean;
|
|
66
|
+
TemplateId: number;
|
|
67
|
+
Name: string;
|
|
68
|
+
Alias: string;
|
|
69
|
+
TemplateType: "Layout" | "Standard";
|
|
70
|
+
LayoutTemplate: string | null;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Create and edit response (refs/api_templates-api.md:565-585). The edit doc omits `TemplateType`;
|
|
74
|
+
* postmark-python requires it (sdk/postmark-python/postmark/models/templates/schemas.py:114).
|
|
75
|
+
*/
|
|
76
|
+
export declare const templateSummaryJson: (t: Template) => {
|
|
77
|
+
TemplateId: number;
|
|
78
|
+
Name: string;
|
|
79
|
+
Active: boolean;
|
|
80
|
+
Alias: string;
|
|
81
|
+
TemplateType: "Layout" | "Standard";
|
|
82
|
+
LayoutTemplate: string | null;
|
|
83
|
+
};
|
|
84
|
+
/** A nullable optional value: null is absent (docs/08 R9). */
|
|
85
|
+
export declare const nullable: <S extends z.ZodType>(schema: S) => z.ZodPreprocess<z.ZodOptional<S>, unknown>;
|
|
86
|
+
/** php sends `TemplateId: 0` beside an alias (docs/08 R10). */
|
|
87
|
+
export declare const templateIdField: z.ZodPreprocess<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>]>>, unknown>;
|
|
88
|
+
/** php sends an empty model as `[]` (docs/08 R10). */
|
|
89
|
+
export declare const templateModelField: z.ZodPreprocess<z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodUnknown>, unknown>>, unknown>;
|
|
90
|
+
/**
|
|
91
|
+
* The standard template and layout a send names. `TemplateId` wins over `TemplateAlias`
|
|
92
|
+
* (refs/api_templates-api.md:181-182). A layout or no reference is 1101 (INFERRED).
|
|
93
|
+
*/
|
|
94
|
+
export declare function sendTemplate(state: State, serverId: number, reference: {
|
|
95
|
+
TemplateId?: number | undefined;
|
|
96
|
+
TemplateAlias?: string | undefined;
|
|
97
|
+
}): {
|
|
98
|
+
template: Template;
|
|
99
|
+
layout: Template | null;
|
|
100
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiError } from "../../errors.js";
|
|
3
|
+
import { intLike, objectOrEmptyArray, parseBody } from "../../http/normalize.js";
|
|
4
|
+
import { Unsupported } from "../../http/respond.js";
|
|
5
|
+
import { parseTemplate } from "../../render/mustachio.js";
|
|
6
|
+
// Templates API rules (docs/06 §3.1–§3.2). Messages marked INFERRED have no documented text.
|
|
7
|
+
export const TEMPLATE_TYPES = ["Standard", "Layout"];
|
|
8
|
+
export const CONTENT_PARTS = ["Subject", "HtmlBody", "TextBody"];
|
|
9
|
+
/** ErrorCode 403 for a field of the wrong type, as in the send pipeline (docs/02 §9 Q13, INFERRED). */
|
|
10
|
+
export const invalidField = (field) => apiError(403, { message: `Invalid request field(s): '${field}'.` });
|
|
11
|
+
/** Parses a body with zod; a failure is 403 naming the first bad field. */
|
|
12
|
+
export function parseOrReject(schema, body) {
|
|
13
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
14
|
+
throw new Unsupported("a send body that is not a JSON object is not captured");
|
|
15
|
+
}
|
|
16
|
+
const result = parseBody(schema, body);
|
|
17
|
+
if (!result.success)
|
|
18
|
+
throw invalidField(String(result.error.issues[0]?.path[0]));
|
|
19
|
+
return result.data;
|
|
20
|
+
}
|
|
21
|
+
/** `Standard` (default) or `Layout` (refs/api_templates-api.md:544); another value is 1122 (INFERRED text). */
|
|
22
|
+
export function parseTemplateType(value) {
|
|
23
|
+
const type = TEMPLATE_TYPES.find((t) => t === (value ?? "Standard"));
|
|
24
|
+
if (type === undefined) {
|
|
25
|
+
throw apiError(1122, {
|
|
26
|
+
message: `Invalid 'TemplateType': '${value}'. Use Standard or Layout.`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return type;
|
|
30
|
+
}
|
|
31
|
+
// refs/api_templates-api.md:466.
|
|
32
|
+
const ALIAS = /^[A-Za-z][A-Za-z0-9._-]*$/;
|
|
33
|
+
// refs/api_templates-api.md:6.
|
|
34
|
+
const MAX_TEMPLATES = 100;
|
|
35
|
+
/**
|
|
36
|
+
* The template a path or send names: digits are an ID (an alias starts with a letter), anything
|
|
37
|
+
* else an alias. An alias matches exactly, active templates first (INFERRED).
|
|
38
|
+
*/
|
|
39
|
+
export function findTemplate(state, serverId, idOrAlias) {
|
|
40
|
+
const own = [...state.templates.values()].filter((t) => t.ServerID === serverId);
|
|
41
|
+
if (/^\d+$/.test(idOrAlias))
|
|
42
|
+
return own.find((t) => t.TemplateId === Number(idOrAlias));
|
|
43
|
+
const matches = own.filter((t) => t.Alias === idOrAlias);
|
|
44
|
+
return matches.find((t) => t.Active) ?? matches[0];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 1101. The alias text is SDK-quoted (sdk/postmark-dotnet/src/Postmark.Tests/ClientTemplateTests.cs:248);
|
|
48
|
+
* the ID text follows it (INFERRED, docs/06 Q9).
|
|
49
|
+
*/
|
|
50
|
+
const notFoundField = (idOrAlias) => (/^\d+$/.test(idOrAlias) ? "TemplateId" : "Alias");
|
|
51
|
+
export const templateNotFound = (idOrAlias) => apiError(1101, {
|
|
52
|
+
message: `The Template's '${notFoundField(idOrAlias)}' associated with this request is not valid or was not found.`,
|
|
53
|
+
});
|
|
54
|
+
export function activeTemplate(state, serverId, idOrAlias) {
|
|
55
|
+
const template = findTemplate(state, serverId, idOrAlias);
|
|
56
|
+
if (template?.Active !== true)
|
|
57
|
+
throw templateNotFound(idOrAlias);
|
|
58
|
+
return template;
|
|
59
|
+
}
|
|
60
|
+
/** An active layout by alias; a missing or standard one is 1101 (refs/api_overview.md:84). */
|
|
61
|
+
export function activeLayout(state, serverId, alias) {
|
|
62
|
+
const layout = findTemplate(state, serverId, alias);
|
|
63
|
+
if (layout?.Active !== true || layout.TemplateType !== "Layout")
|
|
64
|
+
throw templateNotFound(alias);
|
|
65
|
+
return layout;
|
|
66
|
+
}
|
|
67
|
+
const layoutUsers = (state, layout) => [...state.templates.values()].filter((t) => t.Active && t.ServerID === layout.ServerID && t.LayoutTemplate === layout.Alias);
|
|
68
|
+
/**
|
|
69
|
+
* The placeholder rule for one part: a layout body holds `{{{ @content }}}` exactly once
|
|
70
|
+
* (refs/api_templates-api.md:541-542); a standard template holds none (INFERRED).
|
|
71
|
+
*/
|
|
72
|
+
export function layoutRuleError(type, part, placeholders) {
|
|
73
|
+
if (type === "Layout" && part !== "Subject" && placeholders !== 1) {
|
|
74
|
+
return `The layout content placeholder must be present exactly once in the ${part}.`;
|
|
75
|
+
}
|
|
76
|
+
if ((type === "Standard" || part === "Subject") && placeholders > 0) {
|
|
77
|
+
return `The layout content placeholder is only allowed in the body of a layout template.`;
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Checks a template as it will be stored, after create or edit. Error codes per docs/02 §4.4;
|
|
83
|
+
* every message is INFERRED except the 1131 prefix the postmark.js live test reads
|
|
84
|
+
* (sdk/postmark.js/test/integration/Templates.test.ts:122).
|
|
85
|
+
*/
|
|
86
|
+
export function checkTemplate(state, template) {
|
|
87
|
+
if (template.Name === "")
|
|
88
|
+
throw apiError(1120, { message: "The 'Name' field is required." });
|
|
89
|
+
if (template.HtmlBody === null && template.TextBody === null) {
|
|
90
|
+
throw apiError(1120, { message: "Either 'HtmlBody' or 'TextBody' must be specified." });
|
|
91
|
+
}
|
|
92
|
+
if (template.Alias !== null) {
|
|
93
|
+
if (!ALIAS.test(template.Alias)) {
|
|
94
|
+
throw apiError(1122, {
|
|
95
|
+
message: `The 'Alias' '${template.Alias}' is invalid. It must start with a letter and contain only letters, numbers, '.', '-' and '_'.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const other = findTemplate(state, template.ServerID, template.Alias);
|
|
99
|
+
if (other?.Active && other.TemplateId !== template.TemplateId) {
|
|
100
|
+
throw apiError(1122, { message: `The 'Alias' '${template.Alias}' is already in use.` });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (template.TemplateType === "Layout") {
|
|
104
|
+
if (template.Subject !== null) {
|
|
105
|
+
throw apiError(1123, { message: "A layout template cannot have a 'Subject'." });
|
|
106
|
+
}
|
|
107
|
+
if (template.LayoutTemplate !== null) {
|
|
108
|
+
throw apiError(1123, { message: "A layout template cannot use a 'LayoutTemplate'." });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
if (template.Subject === null) {
|
|
113
|
+
throw apiError(1120, { message: "The 'Subject' field is required." });
|
|
114
|
+
}
|
|
115
|
+
if (template.LayoutTemplate !== null)
|
|
116
|
+
activeLayout(state, template.ServerID, template.LayoutTemplate);
|
|
117
|
+
}
|
|
118
|
+
for (const part of CONTENT_PARTS) {
|
|
119
|
+
const source = template[part];
|
|
120
|
+
if (source === null)
|
|
121
|
+
continue;
|
|
122
|
+
const parsed = parseTemplate(source);
|
|
123
|
+
if (!parsed.ok) {
|
|
124
|
+
throw apiError(1122, {
|
|
125
|
+
message: `The '${part}' content is invalid: ${parsed.errors[0]?.Message}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const placeholder = layoutRuleError(template.TemplateType, part, parsed.template.contentPlaceholders);
|
|
129
|
+
if (placeholder !== undefined)
|
|
130
|
+
throw apiError(1131, { message: placeholder });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export function checkCanCreate(state, serverId) {
|
|
134
|
+
const active = [...state.templates.values()].filter((t) => t.ServerID === serverId && t.Active);
|
|
135
|
+
if (active.length >= MAX_TEMPLATES)
|
|
136
|
+
throw apiError(1105);
|
|
137
|
+
}
|
|
138
|
+
/** A layout that templates use keeps its alias and cannot be deleted. */
|
|
139
|
+
export function checkLayoutChange(state, before, after) {
|
|
140
|
+
if (before.TemplateType !== "Layout" || layoutUsers(state, before).length === 0)
|
|
141
|
+
return;
|
|
142
|
+
if (after === null)
|
|
143
|
+
throw apiError(1130);
|
|
144
|
+
if (after.Alias !== before.Alias) {
|
|
145
|
+
throw new Unsupported("renaming the alias of a layout in use is not captured");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A new template without an alias gets one. The dotnet and php live tests read a layout alias they
|
|
150
|
+
* never set (sdk/postmark-php/tests/PostmarkClientTemplatesTest.php:55-57). The postmark-cli pull
|
|
151
|
+
* test creates a standard template without an alias and expects every template pulled: pull stops
|
|
152
|
+
* at the first template without one (sdk/postmark-cli/test/integration/templates.pull.test.ts:47-50,
|
|
153
|
+
* sdk/postmark-cli/src/commands/templates/pull.ts:142-151). The format is INFERRED.
|
|
154
|
+
*/
|
|
155
|
+
export function generatedAlias(state, template) {
|
|
156
|
+
const prefix = `${template.TemplateType.toLowerCase()}-${template.TemplateId}`;
|
|
157
|
+
for (let n = 0;; n += 1) {
|
|
158
|
+
const alias = n === 0 ? prefix : `${prefix}-${n}`;
|
|
159
|
+
if (findTemplate(state, template.ServerID, alias)?.Active !== true)
|
|
160
|
+
return alias;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** GET response, doc field order (refs/api_templates-api.md:457-468). */
|
|
164
|
+
export const templateJson = (t) => ({
|
|
165
|
+
Name: t.Name,
|
|
166
|
+
TemplateId: t.TemplateId,
|
|
167
|
+
Subject: t.Subject,
|
|
168
|
+
HtmlBody: t.HtmlBody,
|
|
169
|
+
TextBody: t.TextBody,
|
|
170
|
+
AssociatedServerId: t.ServerID,
|
|
171
|
+
Active: t.Active,
|
|
172
|
+
Alias: t.Alias,
|
|
173
|
+
TemplateType: t.TemplateType,
|
|
174
|
+
LayoutTemplate: t.LayoutTemplate,
|
|
175
|
+
});
|
|
176
|
+
/** List item (refs/api_templates-api.md:712-731). */
|
|
177
|
+
export const templateListJson = (t) => ({
|
|
178
|
+
Active: t.Active,
|
|
179
|
+
TemplateId: t.TemplateId,
|
|
180
|
+
Name: t.Name,
|
|
181
|
+
Alias: t.Alias,
|
|
182
|
+
TemplateType: t.TemplateType,
|
|
183
|
+
LayoutTemplate: t.LayoutTemplate,
|
|
184
|
+
});
|
|
185
|
+
/**
|
|
186
|
+
* Create and edit response (refs/api_templates-api.md:565-585). The edit doc omits `TemplateType`;
|
|
187
|
+
* postmark-python requires it (sdk/postmark-python/postmark/models/templates/schemas.py:114).
|
|
188
|
+
*/
|
|
189
|
+
export const templateSummaryJson = (t) => ({
|
|
190
|
+
TemplateId: t.TemplateId,
|
|
191
|
+
Name: t.Name,
|
|
192
|
+
Active: t.Active,
|
|
193
|
+
Alias: t.Alias,
|
|
194
|
+
TemplateType: t.TemplateType,
|
|
195
|
+
LayoutTemplate: t.LayoutTemplate,
|
|
196
|
+
});
|
|
197
|
+
/** A nullable optional value: null is absent (docs/08 R9). */
|
|
198
|
+
export const nullable = (schema) => z.preprocess((v) => (v === null ? undefined : v), schema.optional());
|
|
199
|
+
/** php sends `TemplateId: 0` beside an alias (docs/08 R10). */
|
|
200
|
+
export const templateIdField = z.preprocess((v) => (v === 0 || v === null ? undefined : v), intLike.optional());
|
|
201
|
+
/** php sends an empty model as `[]` (docs/08 R10). */
|
|
202
|
+
export const templateModelField = nullable(objectOrEmptyArray(z.record(z.string(), z.unknown())));
|
|
203
|
+
/**
|
|
204
|
+
* The standard template and layout a send names. `TemplateId` wins over `TemplateAlias`
|
|
205
|
+
* (refs/api_templates-api.md:181-182). A layout or no reference is 1101 (INFERRED).
|
|
206
|
+
*/
|
|
207
|
+
export function sendTemplate(state, serverId, reference) {
|
|
208
|
+
const idOrAlias = reference.TemplateId === undefined ? reference.TemplateAlias : String(reference.TemplateId);
|
|
209
|
+
if (idOrAlias === undefined)
|
|
210
|
+
throw templateNotFound("0");
|
|
211
|
+
const template = activeTemplate(state, serverId, idOrAlias);
|
|
212
|
+
if (template.TemplateType !== "Standard")
|
|
213
|
+
throw templateNotFound(idOrAlias);
|
|
214
|
+
const layout = template.LayoutTemplate === null
|
|
215
|
+
? null
|
|
216
|
+
: activeLayout(state, serverId, template.LayoutTemplate);
|
|
217
|
+
return { template, layout };
|
|
218
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TemplateError } from "../../render/mustachio.ts";
|
|
2
|
+
import type { State } from "../../state/store.ts";
|
|
3
|
+
interface PartResult {
|
|
4
|
+
ContentIsValid: boolean;
|
|
5
|
+
ValidationErrors: TemplateError[];
|
|
6
|
+
RenderedContent: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function validateTemplate(state: State, serverId: number, body: unknown): {
|
|
9
|
+
AllContentIsValid: boolean;
|
|
10
|
+
HtmlBody: PartResult | null;
|
|
11
|
+
TextBody: PartResult | null;
|
|
12
|
+
Subject: PartResult | null;
|
|
13
|
+
SuggestedTemplateModel: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
export {};
|