@softure-ai/mailing 0.2.2 → 0.2.4

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +57 -6
  3. package/dist/attachments.d.ts +32 -0
  4. package/dist/attachments.d.ts.map +1 -0
  5. package/dist/attachments.js +68 -0
  6. package/dist/attachments.js.map +1 -0
  7. package/dist/cli/command.js +8 -1
  8. package/dist/cli/command.js.map +1 -1
  9. package/dist/cli/run.d.ts.map +1 -1
  10. package/dist/cli/run.js +10 -20
  11. package/dist/cli/run.js.map +1 -1
  12. package/dist/contract.d.ts +31 -0
  13. package/dist/contract.d.ts.map +1 -1
  14. package/dist/index.d.ts +7 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/jobs/delivery-job.d.ts +8 -3
  19. package/dist/jobs/delivery-job.d.ts.map +1 -1
  20. package/dist/jobs/delivery-job.js +18 -2
  21. package/dist/jobs/delivery-job.js.map +1 -1
  22. package/dist/options.d.ts +5 -0
  23. package/dist/options.d.ts.map +1 -1
  24. package/dist/options.js +18 -0
  25. package/dist/options.js.map +1 -1
  26. package/dist/providers/resend.d.ts.map +1 -1
  27. package/dist/providers/resend.js +10 -0
  28. package/dist/providers/resend.js.map +1 -1
  29. package/dist/server/deliveries.js +1 -1
  30. package/dist/server/deliveries.js.map +1 -1
  31. package/dist/server/index.d.ts +1 -1
  32. package/dist/server/index.d.ts.map +1 -1
  33. package/dist/server/index.js +1 -1
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/server/operator-output.d.ts +6 -0
  36. package/dist/server/operator-output.d.ts.map +1 -1
  37. package/dist/server/operator-output.js +23 -0
  38. package/dist/server/operator-output.js.map +1 -1
  39. package/dist/server/send-mail.d.ts +10 -3
  40. package/dist/server/send-mail.d.ts.map +1 -1
  41. package/dist/server/send-mail.js +57 -13
  42. package/dist/server/send-mail.js.map +1 -1
  43. package/dist/server/validate-mail.d.ts +8 -3
  44. package/dist/server/validate-mail.d.ts.map +1 -1
  45. package/dist/server/validate-mail.js +52 -12
  46. package/dist/server/validate-mail.js.map +1 -1
  47. package/dist/testing/fake-provider.d.ts.map +1 -1
  48. package/dist/testing/fake-provider.js +18 -3
  49. package/dist/testing/fake-provider.js.map +1 -1
  50. package/dist/testing/outbox.d.ts.map +1 -1
  51. package/dist/testing/outbox.js +10 -1
  52. package/dist/testing/outbox.js.map +1 -1
  53. package/module.json +1 -1
  54. package/package.json +1 -1
  55. package/src/attachments.ts +90 -0
  56. package/src/cli/command.ts +10 -2
  57. package/src/cli/run.ts +10 -20
  58. package/src/contract.ts +33 -0
  59. package/src/index.ts +12 -1
  60. package/src/jobs/delivery-job.ts +24 -4
  61. package/src/options.ts +19 -0
  62. package/src/providers/resend.ts +10 -0
  63. package/src/server/deliveries.ts +1 -1
  64. package/src/server/index.ts +1 -1
  65. package/src/server/operator-output.ts +24 -0
  66. package/src/server/send-mail.ts +61 -14
  67. package/src/server/validate-mail.ts +60 -13
  68. package/src/testing/fake-provider.ts +18 -3
  69. package/src/testing/outbox.ts +11 -1
@@ -1,9 +1,11 @@
1
1
  // Sends one mail through the configured provider, with three rules: it never throws for a failed
2
2
  // send (every failure is a result), nothing of the mail (address, subject, body, key) reaches a log
3
3
  // or the result, and `headers` cannot replace the envelope or the sender (a mail's own reply-to is
4
- // the `replyTo` field, checked like `to`). List mail (any kind but `transactional`) also gets a signed unsubscribe
4
+ // the `replyTo` field, checked like `to`). Attachments go only to a provider that declares it delivers them, and a
5
+ // log line describes them by count, content type and size, never by name or content. List mail (any kind but `transactional`) also gets a signed unsubscribe
5
6
  // link in a footer and the RFC 8058 headers, and is refused for a recipient who unsubscribed.
6
7
  import { err, ok, selectMessages } from "@softure-ai/core";
8
+ import { getContentTypeEssence } from "../attachments.js";
7
9
  import { TRANSACTIONAL_KIND, } from "../contract.js";
8
10
  import { addHtmlFooter, addTextFooter, getListUnsubscribeHeaders } from "./list-mail.js";
9
11
  import { getMailingModule, getMailingOptions } from "./options.js";
@@ -20,42 +22,78 @@ import { validateMail } from "./validate-mail.js";
20
22
  * Throws only when the module is not enabled, or for list mail without `context.db`.
21
23
  */
22
24
  export async function sendMail(context, mail, options = {}) {
23
- const { provider, timeoutMs } = getMailingOptions(context.config);
24
- const validation = validateMail(mail, options);
25
+ const { provider, timeoutMs, attachments: limits } = getMailingOptions(context.config);
26
+ const validation = validateMail(mail, options, limits);
25
27
  if (!validation.ok) {
26
28
  logFailure(provider, "mailing.invalid_input", { fields: validation.fields });
27
29
  return err("mailing.invalid_input");
28
30
  }
29
31
  const valid = validation.value;
32
+ if (!canCarryAttachments(provider, valid))
33
+ return refuseAttachments(provider, valid);
30
34
  const secret = readUnsubscribeSecrets().current;
31
35
  if (valid.kind !== TRANSACTIONAL_KIND) {
32
36
  const refusal = await checkListMail(context, valid, secret);
33
37
  if (refusal !== null) {
34
- logFailure(provider, refusal.error, { cause: refusal.cause });
38
+ logFailure(provider, refusal.error, { cause: refusal.cause, attachments: valid.attachments });
35
39
  return err(refusal.error);
36
40
  }
37
41
  }
38
- const outcome = await callProvider(provider, composeMessage(context.config, valid, secret), timeoutMs);
42
+ return deliver(provider, composeMessage(context.config, valid, secret), timeoutMs);
43
+ }
44
+ /**
45
+ * `sendMail` without the suppression check, for `softure-mail test` without a database: its recipient is always the
46
+ * operator's own `testAddress`. List mail still needs the secret. Internal: not exported from `/server`, so no app
47
+ * code can send list mail to a real recipient past the suppression list.
48
+ */
49
+ export async function sendMailUnchecked(config, mail, options = {}) {
50
+ const { provider, timeoutMs, attachments: limits } = getMailingOptions(config);
51
+ const validation = validateMail(mail, options, limits);
52
+ if (!validation.ok) {
53
+ logFailure(provider, "mailing.invalid_input", { fields: validation.fields });
54
+ return err("mailing.invalid_input");
55
+ }
56
+ if (!canCarryAttachments(provider, validation.value))
57
+ return refuseAttachments(provider, validation.value);
58
+ const secret = readUnsubscribeSecrets().current;
59
+ if (validation.value.kind !== TRANSACTIONAL_KIND && secret === null) {
60
+ logFailure(provider, "mailing.unavailable", { cause: "no_unsubscribe_secret", attachments: validation.value.attachments });
61
+ return err("mailing.unavailable");
62
+ }
63
+ return deliver(provider, composeMessage(config, validation.value, secret), timeoutMs);
64
+ }
65
+ /** Hands `message` to the provider and maps its outcome to the result, logging a failure. */
66
+ async function deliver(provider, message, timeoutMs) {
67
+ const outcome = await callProvider(provider, message, timeoutMs);
39
68
  if (outcome.status === "sent") {
40
69
  return ok({ id: outcome.id, provider: provider.name });
41
70
  }
42
71
  const code = FAILURE_CODES[outcome.status];
43
- logFailure(provider, code, { status: outcome.httpStatus });
72
+ logFailure(provider, code, { status: outcome.httpStatus, attachments: message.attachments ?? [] });
44
73
  return {
45
74
  ...err(code),
46
75
  ...(outcome.httpStatus === undefined ? {} : { httpStatus: outcome.httpStatus }),
47
76
  ...(outcome.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: outcome.retryAfterSeconds }),
48
77
  };
49
78
  }
79
+ /** False for a mail with attachments and a provider that does not declare it delivers them. */
80
+ function canCarryAttachments(provider, mail) {
81
+ return mail.attachments.length === 0 || provider.supportsAttachments === true;
82
+ }
83
+ function refuseAttachments(provider, mail) {
84
+ logFailure(provider, "mailing.invalid_input", { fields: ["attachments"], cause: "provider_without_attachments", attachments: mail.attachments });
85
+ return err("mailing.invalid_input");
86
+ }
50
87
  /**
51
- * Exactly what `sendMail` would hand the provider for `mail`: the sender, the reply-to, and for list mail the
52
- * footer with a link signed for `mail.to` and the RFC 8058 headers. Sends nothing, logs nothing and needs no
53
- * database: it does not check the suppression list, so it renders a mail `sendMail` would refuse as suppressed.
88
+ * Exactly what `sendMail` would hand the provider for `mail`: the sender, the reply-to, the attachments, and for list
89
+ * mail the footer with a link signed for `mail.to` and the RFC 8058 headers. Sends nothing, logs nothing and needs no
90
+ * database: it does not check the suppression list, so it renders a mail `sendMail` would refuse as suppressed, nor
91
+ * whether the provider delivers attachments.
54
92
  * The links in it are credentials for `mail.to`. Throws only when the module is not enabled.
55
93
  */
56
94
  export function previewMail(config, mail, options = {}) {
57
95
  const { env, ...sendOptions } = options;
58
- const validation = validateMail(mail, sendOptions);
96
+ const validation = validateMail(mail, sendOptions, getMailingOptions(config).attachments);
59
97
  if (!validation.ok)
60
98
  return { ...err("mailing.invalid_input"), fields: validation.fields };
61
99
  const secret = readUnsubscribeSecrets(env).current;
@@ -95,8 +133,8 @@ async function checkListMail(context, mail, secret) {
95
133
  */
96
134
  function composeMessage(config, mail, secret) {
97
135
  const { from, replyTo } = getMailingOptions(config);
98
- const { idempotencyKey, kind, replyTo: mailReplyTo, ...content } = mail;
99
- const envelope = { from, replyTo: mailReplyTo ?? replyTo ?? null, idempotencyKey };
136
+ const { idempotencyKey, kind, replyTo: mailReplyTo, attachments, ...content } = mail;
137
+ const envelope = { from, replyTo: mailReplyTo ?? replyTo ?? null, idempotencyKey, ...(attachments.length === 0 ? {} : { attachments }) };
100
138
  if (kind === TRANSACTIONAL_KIND || secret === null)
101
139
  return { ...content, ...envelope };
102
140
  const links = buildUnsubscribeLinks(config, mail.to, secret);
@@ -160,7 +198,9 @@ function isHttpStatus(value) {
160
198
  }
161
199
  /**
162
200
  * The module's only log line. Deliberately takes nothing but the provider, the code, a status,
163
- * field names and a fixed cause: anything added "for diagnosis" is where an address leaks first.
201
+ * field names, a fixed cause and the attachments' content types and sizes: anything added "for
202
+ * diagnosis" is where an address leaks first. Filenames stay out like the subject: they are the
203
+ * app's text and often name a person or an invoice.
164
204
  */
165
205
  function logFailure(provider, code, details) {
166
206
  const parts = [`provider=${provider.name}`, `reason=${code.slice("mailing.".length)}`, `status=${details.status === undefined ? "none" : String(details.status)}`];
@@ -168,6 +208,10 @@ function logFailure(provider, code, details) {
168
208
  parts.push(`fields=${details.fields.join(",")}`);
169
209
  if (details.cause !== undefined)
170
210
  parts.push(`cause=${details.cause}`);
211
+ if (details.attachments !== undefined && details.attachments.length > 0) {
212
+ const attachments = details.attachments.map((attachment) => `${getContentTypeEssence(attachment.contentType)}:${String(attachment.content.byteLength)}`);
213
+ parts.push(`attachments=${attachments.join(",")}`);
214
+ }
171
215
  console.error(`mailing: send failed ${parts.join(" ")}`);
172
216
  }
173
217
  //# sourceMappingURL=send-mail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"send-mail.js","sourceRoot":"","sources":["../../src/server/send-mail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"send-mail.js","sourceRoot":"","sources":["../../src/server/send-mail.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { type AttachmentLimits } from "../attachments.js";
2
+ import { type MailAttachment } from "../contract.js";
1
3
  export interface ValidMail {
2
4
  readonly to: string;
3
5
  readonly subject: string;
@@ -9,6 +11,8 @@ export interface ValidMail {
9
11
  /** `transactional` or a list kind. */
10
12
  readonly kind: string;
11
13
  readonly idempotencyKey: string | null;
14
+ /** Empty when the mail has none. */
15
+ readonly attachments: readonly MailAttachment[];
12
16
  }
13
17
  export type MailValidation = {
14
18
  readonly ok: true;
@@ -18,8 +22,9 @@ export type MailValidation = {
18
22
  readonly fields: readonly string[];
19
23
  };
20
24
  /**
21
- * Checks one mail and its send options. `fields` lists what failed (`to`, `subject`, `text`,
22
- * `html`, `headers`, `replyTo`, `kind`, `idempotencyKey`, or `mail` when the input is not an object).
25
+ * Checks one mail and its send options, its attachments against `limits` (default: the module's defaults; `sendMail`
26
+ * passes the configured ones). `fields` lists what failed (`to`, `subject`, `text`, `html`, `headers`, `replyTo`,
27
+ * `kind`, `attachments`, `idempotencyKey`, or `mail` when the input is not an object).
23
28
  */
24
- export declare function validateMail(mail: unknown, options?: unknown): MailValidation;
29
+ export declare function validateMail(mail: unknown, options?: unknown, limits?: AttachmentLimits): MailValidation;
25
30
  //# sourceMappingURL=validate-mail.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-mail.d.ts","sourceRoot":"","sources":["../../src/server/validate-mail.ts"],"names":[],"mappings":"AAsCA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3I;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,OAAY,GAAG,cAAc,CAajF"}
1
+ {"version":3,"file":"validate-mail.d.ts","sourceRoot":"","sources":["../../src/server/validate-mail.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyF,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACjJ,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAmEzE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,oCAAoC;IACpC,QAAQ,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,CAAC;CACjD;AAED,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAE3I;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,OAAY,EAAE,MAAM,GAAE,gBAA4C,GAAG,cAAc,CAuBvI"}
@@ -2,6 +2,7 @@
2
2
  // process. Failures name the fields, never their values (an address is personal data).
3
3
  import { z } from "zod";
4
4
  import { hasLineBreak, isHeaderName, isIdempotencyKey, isListMailHeader, isMailKind, isReservedHeader, isSingleAddress, MAX_SUBJECT_LENGTH } from "../address.js";
5
+ import { DEFAULT_ATTACHMENT_LIMITS, getContentTypeEssence, isAttachmentFilename, isContentType } from "../attachments.js";
5
6
  import { TRANSACTIONAL_KIND } from "../contract.js";
6
7
  const headersSchema = z.record(z.string(), z.string()).superRefine((headers, context) => {
7
8
  for (const [name, value] of Object.entries(headers)) {
@@ -10,7 +11,22 @@ const headersSchema = z.record(z.string(), z.string()).superRefine((headers, con
10
11
  }
11
12
  }
12
13
  });
13
- const mailSchema = z.object({
14
+ const attachmentSchema = z.object({
15
+ filename: z.string().refine(isAttachmentFilename),
16
+ contentType: z.string().trim().refine(isContentType),
17
+ // A Node Buffer is a Uint8Array too; an empty file is almost always a caller's bug.
18
+ content: z.custom((value) => value instanceof Uint8Array && value.byteLength > 0),
19
+ });
20
+ /** The attachments within `limits`: count, total bytes and an allowed content type for each. */
21
+ function createAttachmentsSchema(limits) {
22
+ const allowed = new Set(limits.contentTypes);
23
+ return z
24
+ .array(attachmentSchema)
25
+ .max(limits.maxCount)
26
+ .refine((attachments) => attachments.every((attachment) => allowed.has(getContentTypeEssence(attachment.contentType))))
27
+ .refine((attachments) => attachments.reduce((total, attachment) => total + attachment.content.byteLength, 0) <= limits.maxTotalBytes);
28
+ }
29
+ const mailFields = {
14
30
  to: z.string().trim().refine(isSingleAddress),
15
31
  subject: z
16
32
  .string()
@@ -23,31 +39,55 @@ const mailSchema = z.object({
23
39
  headers: headersSchema.optional(),
24
40
  replyTo: z.string().trim().refine(isSingleAddress).optional(),
25
41
  kind: z.string().refine(isMailKind).default(TRANSACTIONAL_KIND),
26
- }).superRefine((mail, context) => {
27
- // The module writes these two on list mail; a second, unsigned copy would confuse clients.
28
- if (mail.kind !== TRANSACTIONAL_KIND && Object.keys(mail.headers ?? {}).some(isListMailHeader)) {
29
- context.addIssue({ code: "custom", path: ["headers"], message: "list mail gets its unsubscribe headers from the module" });
42
+ };
43
+ function createMailSchema(limits) {
44
+ return z.object({ ...mailFields, attachments: createAttachmentsSchema(limits).optional() }).superRefine((mail, context) => {
45
+ // The module writes these two on list mail; a second, unsigned copy would confuse clients.
46
+ if (mail.kind !== TRANSACTIONAL_KIND && Object.keys(mail.headers ?? {}).some(isListMailHeader)) {
47
+ context.addIssue({ code: "custom", path: ["headers"], message: "list mail gets its unsubscribe headers from the module" });
48
+ }
49
+ });
50
+ }
51
+ /** One schema per limits object: the configured limits are parsed once per config, so this builds each once. */
52
+ const mailSchemas = new WeakMap();
53
+ function getMailSchema(limits) {
54
+ let schema = mailSchemas.get(limits);
55
+ if (schema === undefined) {
56
+ schema = createMailSchema(limits);
57
+ mailSchemas.set(limits, schema);
30
58
  }
31
- });
59
+ return schema;
60
+ }
32
61
  const optionsSchema = z.object({
33
62
  idempotencyKey: z.string().refine(isIdempotencyKey).optional(),
34
63
  });
35
64
  /**
36
- * Checks one mail and its send options. `fields` lists what failed (`to`, `subject`, `text`,
37
- * `html`, `headers`, `replyTo`, `kind`, `idempotencyKey`, or `mail` when the input is not an object).
65
+ * Checks one mail and its send options, its attachments against `limits` (default: the module's defaults; `sendMail`
66
+ * passes the configured ones). `fields` lists what failed (`to`, `subject`, `text`, `html`, `headers`, `replyTo`,
67
+ * `kind`, `attachments`, `idempotencyKey`, or `mail` when the input is not an object).
38
68
  */
39
- export function validateMail(mail, options = {}) {
40
- const parsedMail = mailSchema.safeParse(mail);
69
+ export function validateMail(mail, options = {}, limits = DEFAULT_ATTACHMENT_LIMITS) {
70
+ const parsedMail = getMailSchema(limits).safeParse(mail);
41
71
  const parsedOptions = optionsSchema.safeParse(options ?? {});
42
72
  if (!parsedMail.success || !parsedOptions.success) {
43
73
  const issues = [...(parsedMail.error?.issues ?? []), ...(parsedOptions.error?.issues ?? [])];
44
74
  const fields = new Set(issues.map((issue) => (issue.path.length === 0 ? "mail" : String(issue.path[0]))));
45
75
  return { ok: false, fields: [...fields] };
46
76
  }
47
- const { to, subject, text, html, headers, replyTo, kind } = parsedMail.data;
77
+ const { to, subject, text, html, headers, replyTo, kind, attachments = [] } = parsedMail.data;
48
78
  return {
49
79
  ok: true,
50
- value: { to, subject, text, html: html ?? null, headers: headers ?? {}, replyTo: replyTo ?? null, kind, idempotencyKey: parsedOptions.data.idempotencyKey ?? null },
80
+ value: {
81
+ to,
82
+ subject,
83
+ text,
84
+ html: html ?? null,
85
+ headers: headers ?? {},
86
+ replyTo: replyTo ?? null,
87
+ kind,
88
+ idempotencyKey: parsedOptions.data.idempotencyKey ?? null,
89
+ attachments,
90
+ },
51
91
  };
52
92
  }
53
93
  //# sourceMappingURL=validate-mail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-mail.js","sourceRoot":"","sources":["../../src/server/validate-mail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate-mail.js","sourceRoot":"","sources":["../../src/server/validate-mail.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fake-provider.d.ts","sourceRoot":"","sources":["../../src/testing/fake-provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAErF,4EAA4E;AAC5E,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,4FAA4F;IAC5F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;CAC3G;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,CAAC;IACvC,0DAA0D;IAC1D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAoCxF"}
1
+ {"version":3,"file":"fake-provider.d.ts","sourceRoot":"","sources":["../../src/testing/fake-provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAErF,4EAA4E;AAC5E,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,4FAA4F;IAC5F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;CAC3G;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,CAAC;IACvC,0DAA0D;IAC1D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CA0CxF"}
@@ -1,6 +1,6 @@
1
1
  // A provider that sends nothing: it captures mail in memory (`provider.sent`) and, with an outbox
2
2
  // file, appends each mail as one JSON line for a test in another process (an e2e against
3
- // `next start`). It honours idempotency keys like a real provider: a repeated key answers the
3
+ // `next start`), attachment bytes as base64. It honours idempotency keys like a real provider: a repeated key answers the
4
4
  // first id and captures nothing new.
5
5
  import { appendFile } from "node:fs/promises";
6
6
  export function fakeMailProvider(options = {}) {
@@ -9,6 +9,7 @@ export function fakeMailProvider(options = {}) {
9
9
  let counter = 0;
10
10
  return {
11
11
  name: "fake",
12
+ supportsAttachments: true,
12
13
  get sent() {
13
14
  return [...sent];
14
15
  },
@@ -29,9 +30,14 @@ export function fakeMailProvider(options = {}) {
29
30
  if (knownId !== undefined)
30
31
  return { status: "sent", id: knownId };
31
32
  counter += 1;
32
- const captured = { ...message, headers: { ...message.headers }, id: `fake-${String(counter)}-${crypto.randomUUID()}` };
33
+ const captured = {
34
+ ...message,
35
+ headers: { ...message.headers },
36
+ ...(message.attachments === undefined ? {} : { attachments: message.attachments.map((attachment) => ({ ...attachment, content: attachment.content.slice() })) }),
37
+ id: `fake-${String(counter)}-${crypto.randomUUID()}`,
38
+ };
33
39
  if (options.outboxFile !== undefined) {
34
- await appendFile(options.outboxFile, `${JSON.stringify(captured)}\n`, "utf8");
40
+ await appendFile(options.outboxFile, `${JSON.stringify(toOutboxLine(captured))}\n`, "utf8");
35
41
  }
36
42
  sent.push(captured);
37
43
  if (message.idempotencyKey !== null)
@@ -40,4 +46,13 @@ export function fakeMailProvider(options = {}) {
40
46
  },
41
47
  };
42
48
  }
49
+ /** A captured mail as one outbox line: attachment bytes as base64 (`readMailOutbox` decodes them). */
50
+ function toOutboxLine(mail) {
51
+ if (mail.attachments === undefined)
52
+ return mail;
53
+ return {
54
+ ...mail,
55
+ attachments: mail.attachments.map((attachment) => ({ ...attachment, content: Buffer.from(attachment.content).toString("base64") })),
56
+ };
57
+ }
43
58
  //# sourceMappingURL=fake-provider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fake-provider.js","sourceRoot":"","sources":["../../src/testing/fake-provider.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,yFAAyF;AACzF,8FAA8F;AAC9F,qCAAqC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAyB9C,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI,IAAI;YACN,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,KAAK;YACH,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAChB,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO;YAChB,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBAC9E,2DAA2D;gBAC3D,OAAO,CAAC,KAAK,CAAC,2GAA2G,CAAC,CAAC;gBAC3H,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;YACnC,CAAC;YACD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,OAAO,CAAC;YAE1C,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YACnG,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;YAElE,OAAO,IAAI,CAAC,CAAC;YACb,MAAM,QAAQ,GAAiB,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,QAAQ,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,CAAC;YACrI,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACrC,MAAM,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAChF,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpB,IAAI,OAAO,CAAC,cAAc,KAAK,IAAI;gBAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;YACvF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC;QAC7C,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"fake-provider.js","sourceRoot":"","sources":["../../src/testing/fake-provider.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../src/testing/outbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAcvD,gHAAgH;AAChH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAajH"}
1
+ {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../src/testing/outbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAkBvD,gHAAgH;AAChH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAajH"}
@@ -11,6 +11,10 @@ const capturedMailSchema = z.object({
11
11
  html: z.string().nullable(),
12
12
  headers: z.record(z.string(), z.string()),
13
13
  idempotencyKey: z.string().nullable(),
14
+ // Base64 in the file, bytes again here; lines written before attachments existed have none.
15
+ attachments: z
16
+ .array(z.object({ filename: z.string(), contentType: z.string(), content: z.base64().transform((content) => new Uint8Array(Buffer.from(content, "base64"))) }))
17
+ .optional(),
14
18
  });
15
19
  /** Every mail in the outbox, oldest first, optionally only those to `to`. A missing file is an empty outbox. */
16
20
  export async function readMailOutbox(file, filter = {}) {
@@ -26,7 +30,12 @@ export async function readMailOutbox(file, filter = {}) {
26
30
  return text
27
31
  .split("\n")
28
32
  .filter((line) => line !== "")
29
- .map((line) => capturedMailSchema.parse(JSON.parse(line)))
33
+ .map((line) => toCapturedMail(capturedMailSchema.parse(JSON.parse(line))))
30
34
  .filter((mail) => filter.to === undefined || mail.to === filter.to);
31
35
  }
36
+ /** A parsed line without an `attachments: undefined` key, so a mail without attachments equals what was sent. */
37
+ function toCapturedMail(line) {
38
+ const { attachments, ...mail } = line;
39
+ return attachments === undefined ? mail : { ...mail, attachments };
40
+ }
32
41
  //# sourceMappingURL=outbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/testing/outbox.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IACzC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC;AAEH,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,SAAmC,EAAE;IACtF,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAClE,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;SACzD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../src/testing/outbox.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IACzC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,4FAA4F;IAC5F,WAAW,EAAE,CAAC;SACX,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC9J,QAAQ,EAAE;CACd,CAAC,CAAC;AAEH,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,SAAmC,EAAE;IACtF,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAClE,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACzE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,iHAAiH;AACjH,SAAS,cAAc,CAAC,IAAyC;IAC/D,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACtC,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,CAAC;AACrE,CAAC"}
package/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "mailing",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "dependsOn": { "jobs": "*?" },
5
5
  "dbSchema": "mailing",
6
6
  "tables": ["suppressions", "campaigns", "deliveries"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/mailing",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "SOFTURE AI mailing module: sendMail over a provider adapter (Resend first) with plain-text and HTML bodies, sender and reply-to from config, idempotency keys, a timeout and a typed result; signed one-click unsubscribe (RFC 8058) with a suppression list for list mail; an exactly-once delivery ledger, campaigns sent from a content file by the softure-mail command, and a sender DNS check; a fake provider for tests.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,90 @@
1
+ // The rules for mail attachments: the default limits and allow-list, the filename and content type checks, and the
2
+ // summary that stands in for an attachment wherever the bytes must not go (logs, dry runs, an app's records).
3
+ import type { AttachmentSummary, MailAttachment } from "./contract.js";
4
+
5
+ /** Files per mail when `attachments.maxCount` is not set. */
6
+ export const DEFAULT_MAX_ATTACHMENTS = 10;
7
+
8
+ /**
9
+ * The most raw bytes all attachments of one mail may hold, and the default. Base64 grows them by a third on the
10
+ * wire: 28,000,000 bytes encode to about 37.3 MB, under the 40 MB a mail may have at Resend and similar providers,
11
+ * with room for the bodies. `attachments.maxTotalBytes` can only lower it.
12
+ */
13
+ export const MAX_ATTACHMENT_BYTES = 28_000_000;
14
+
15
+ /**
16
+ * The content types allowed when `attachments.contentTypes` is not set: documents, data exports, images and calendar
17
+ * invites. HTML, SVG, scripts and executables are left out: a mail client may run or render them.
18
+ */
19
+ export const DEFAULT_ATTACHMENT_CONTENT_TYPES: readonly string[] = [
20
+ "application/pdf",
21
+ "text/plain",
22
+ "text/csv",
23
+ "text/calendar",
24
+ "application/json",
25
+ "application/xml",
26
+ "text/xml",
27
+ "application/zip",
28
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
29
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
30
+ "image/png",
31
+ "image/jpeg",
32
+ "image/gif",
33
+ "image/webp",
34
+ ];
35
+
36
+ export const MAX_ATTACHMENT_FILENAME_LENGTH = 255;
37
+
38
+ /** The limits a mail's attachments are checked against (`mailing({ attachments })`, resolved). */
39
+ export interface AttachmentLimits {
40
+ readonly maxCount: number;
41
+ readonly maxTotalBytes: number;
42
+ /** Lowercase `type/subtype` values. */
43
+ readonly contentTypes: readonly string[];
44
+ }
45
+
46
+ export const DEFAULT_ATTACHMENT_LIMITS: AttachmentLimits = {
47
+ maxCount: DEFAULT_MAX_ATTACHMENTS,
48
+ maxTotalBytes: MAX_ATTACHMENT_BYTES,
49
+ contentTypes: DEFAULT_ATTACHMENT_CONTENT_TYPES,
50
+ };
51
+
52
+ const TOKEN = "[A-Za-z0-9!#$&^_.+-]+";
53
+ const CONTENT_TYPE = new RegExp(`^${TOKEN}/${TOKEN}(\\s*;\\s*${TOKEN}=("[^"\\r\\n]*"|${TOKEN}))*$`);
54
+
55
+ /** A plain filename: 1 to 255 characters, not blank, no `/` or `\`, no control characters, not `.` or `..`. */
56
+ export function isAttachmentFilename(value: string): boolean {
57
+ return (
58
+ value.trim() !== "" &&
59
+ value.length <= MAX_ATTACHMENT_FILENAME_LENGTH &&
60
+ !value.includes("/") &&
61
+ !value.includes("\\") &&
62
+ !hasControlCharacter(value) &&
63
+ value !== "." &&
64
+ value !== ".."
65
+ );
66
+ }
67
+
68
+ /** A C0 control character or DEL. */
69
+ function hasControlCharacter(value: string): boolean {
70
+ for (let index = 0; index < value.length; index += 1) {
71
+ const code = value.charCodeAt(index);
72
+ if (code < 0x20 || code === 0x7f) return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ /** A MIME type `type/subtype`, optionally with parameters (`text/csv; charset=utf-8`). */
78
+ export function isContentType(value: string): boolean {
79
+ return CONTENT_TYPE.test(value);
80
+ }
81
+
82
+ /** The `type/subtype` of a content type, lowercase, without its parameters. */
83
+ export function getContentTypeEssence(value: string): string {
84
+ return (value.split(";")[0] ?? "").trim().toLowerCase();
85
+ }
86
+
87
+ /** Every attachment without its bytes: filename, content type and size. Safe to keep in an app's own records. */
88
+ export function describeAttachments(attachments: readonly MailAttachment[]): AttachmentSummary[] {
89
+ return attachments.map(({ filename, contentType, content }) => ({ filename, contentType, size: content.byteLength }));
90
+ }
@@ -1,7 +1,7 @@
1
1
  // The `softure-mail` bin. It loads the app's config through `@softure-ai/core/cli`, like
2
2
  // `softure migrate` (@softure-ai/db): the file must be loadable by Node itself (`.js`/`.mjs`, or `.ts`
3
3
  // where Node strips types). Anything else uses an app script with `runMailCli` instead (see run.ts).
4
- import { loadAppConfig, takeConfigOption } from "@softure-ai/core/cli";
4
+ import { loadAppConfig, takeConfigOption, type DatabaseRequirement } from "@softure-ai/core/cli";
5
5
  import { EXIT_FAILED, EXIT_USAGE, MAIL_USAGE, runMailCli, type CliOutput } from "./run.js";
6
6
 
7
7
  export interface RunMailCommandOptions {
@@ -29,7 +29,7 @@ export async function runMailCommand(options: RunMailCommandOptions): Promise<nu
29
29
  const { configPath, argv } = taken;
30
30
  if (!needsConfig(argv) && configPath === undefined) return runMailCli({ argv, cwd: options.cwd, output });
31
31
 
32
- const loaded = await loadAppConfig({ cwd: options.cwd, configPath, appScript: APP_SCRIPT });
32
+ const loaded = await loadAppConfig({ cwd: options.cwd, configPath, appScript: APP_SCRIPT, database: getDatabaseRequirement(argv) });
33
33
  if (!loaded.ok) {
34
34
  output.error(`softure-mail: ${loaded.problem}`);
35
35
  return EXIT_FAILED;
@@ -42,3 +42,11 @@ function needsConfig(argv: readonly string[]): boolean {
42
42
  if (argv.length === 0 || argv.includes("--help")) return false;
43
43
  return !(argv[0] === "dns" && argv.some((arg) => arg === "--domain" || arg.startsWith("--domain=")));
44
44
  }
45
+
46
+ /**
47
+ * `test` and `dns` never need a connection, so a missing or empty database URL loads as `database: null` (a CI job
48
+ * with only the provider key and the unsubscribe secret); the commands that connect keep refusing such a config.
49
+ */
50
+ function getDatabaseRequirement(argv: readonly string[]): DatabaseRequirement {
51
+ return argv[0] === "test" || argv[0] === "dns" ? "optional" : "required";
52
+ }