@hogsend/engine 0.13.0 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/engine",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,14 +40,14 @@
40
40
  "svix": "^1.95.1",
41
41
  "winston": "^3.19.0",
42
42
  "zod": "^4.4.3",
43
- "@hogsend/core": "^0.13.0",
44
- "@hogsend/db": "^0.13.0",
45
- "@hogsend/email": "^0.13.0",
46
- "@hogsend/plugin-posthog": "^0.13.0",
47
- "@hogsend/plugin-resend": "^0.13.0"
43
+ "@hogsend/core": "^0.13.1",
44
+ "@hogsend/db": "^0.13.1",
45
+ "@hogsend/email": "^0.13.1",
46
+ "@hogsend/plugin-posthog": "^0.13.1",
47
+ "@hogsend/plugin-resend": "^0.13.1"
48
48
  },
49
49
  "optionalDependencies": {
50
- "@hogsend/plugin-postmark": "^0.13.0"
50
+ "@hogsend/plugin-postmark": "^0.13.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^22.15.3",
package/src/env.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { createEnv } from "@t3-oss/env-core";
2
2
  import { z } from "zod";
3
+ import { addrSpecOf } from "./lib/from-address.js";
4
+
5
+ // A from address may be bare ("doug@hogsend.com") or carry a display name
6
+ // ("Doug at Hogsend <doug@hogsend.com>") — both are valid provider wire
7
+ // formats. Domain derivation (lib/from-address.ts) parses either form.
8
+ const fromAddress = z.string().refine((value) => addrSpecOf(value) !== null, {
9
+ message: 'Must be an email address or "Display Name <email>"',
10
+ });
3
11
 
4
12
  /**
5
13
  * The HTTP API contract version — surfaced in the OpenAPI document
@@ -52,7 +60,7 @@ export const env = createEnv({
52
60
  // (container.ts) and the future `emailProvidersFromEnv` preset. With this
53
61
  // optional, a Postmark-only deploy boots without a Resend key.
54
62
  RESEND_API_KEY: z.string().min(1).optional(),
55
- RESEND_FROM_EMAIL: z.string().email().default("noreply@hogsend.com"),
63
+ RESEND_FROM_EMAIL: fromAddress.default("noreply@hogsend.com"),
56
64
  // --- Provider-neutral email config (BYO email provider) ---
57
65
  // The active email provider id the container resolves from the
58
66
  // EmailProviderRegistry. Absent → "resend" (today's byte-for-byte default).
@@ -60,7 +68,7 @@ export const env = createEnv({
60
68
  // Neutral default-from address. The mailer's `defaultFrom` is
61
69
  // `EMAIL_FROM ?? RESEND_FROM_EMAIL`, so an unset EMAIL_FROM keeps today's
62
70
  // Resend-named default.
63
- EMAIL_FROM: z.string().email().optional(),
71
+ EMAIL_FROM: fromAddress.optional(),
64
72
  // The sending domain the domain-status service reports on. OVERRIDES the
65
73
  // default derivation (host part of EMAIL_FROM, falling back to the host of
66
74
  // RESEND_FROM_EMAIL) — set it when you send from a subaddress domain that
package/src/index.ts CHANGED
@@ -203,6 +203,7 @@ export type {
203
203
  // --- Enrollment guards ---
204
204
  export { checkEmailPreferences } from "./lib/enrollment-guards.js";
205
205
  export { isFrequencyCapped } from "./lib/frequency-cap.js";
206
+ export { addrSpecOf, hostOfFromAddress } from "./lib/from-address.js";
206
207
  export { hatchet } from "./lib/hatchet.js";
207
208
  // --- Ingestion pipeline ---
208
209
  export {
@@ -1,5 +1,6 @@
1
1
  import type { DomainStatus, EmailProvider } from "@hogsend/core";
2
2
  import type { env as envSchema } from "../env.js";
3
+ import { hostOfFromAddress } from "./from-address.js";
3
4
  import type { Logger } from "./logger.js";
4
5
 
5
6
  /**
@@ -91,13 +92,11 @@ function isPermissionDeniedMessage(message: string): boolean {
91
92
  return /\bdomains API (?:request failed with status )?40[13]\b/.test(message);
92
93
  }
93
94
 
94
- /** Extract the host part of an email address ("hello@x.com" → "x.com"). */
95
- function hostPartOf(email: string | undefined): string | null {
96
- if (!email) return null;
97
- const at = email.lastIndexOf("@");
98
- if (at === -1 || at === email.length - 1) return null;
99
- return email.slice(at + 1).toLowerCase();
100
- }
95
+ /**
96
+ * Extract the host part of a configured from address ("hello@x.com" or
97
+ * "Name <hello@x.com>" → "x.com") display-name aware via from-address.ts.
98
+ */
99
+ const hostPartOf = hostOfFromAddress;
101
100
 
102
101
  /** The Resend unverified-domain from-address fallback (so a redirected mail
103
102
  * still delivers while the real sending domain isn't verified yet). */
@@ -0,0 +1,29 @@
1
+ /**
2
+ * From-address helpers. A configured from address may be a bare addr-spec
3
+ * ("doug@hogsend.com") or carry a display name ("Doug at Hogsend
4
+ * <doug@hogsend.com>") — both are valid on the wire for every supported
5
+ * provider. These helpers parse either form so env validation and
6
+ * domain derivation agree on what the address part is.
7
+ */
8
+
9
+ const ADDR_SPEC_RE = /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/;
10
+
11
+ /**
12
+ * Extract the addr-spec from a from address ("Doug <d@x.com>" → "d@x.com",
13
+ * "d@x.com" → "d@x.com"). Returns null when no valid address is present.
14
+ */
15
+ export function addrSpecOf(value: string | undefined): string | null {
16
+ if (!value) return null;
17
+ const match = value.trim().match(/^[^<>]*<([^<>]+)>$/);
18
+ const addr = (match?.[1] ?? value).trim();
19
+ return ADDR_SPEC_RE.test(addr) ? addr.toLowerCase() : null;
20
+ }
21
+
22
+ /** Host part of a from address ("Doug <d@x.com>" → "x.com"). */
23
+ export function hostOfFromAddress(value: string | undefined): string | null {
24
+ const addr = addrSpecOf(value);
25
+ if (!addr) return null;
26
+ const at = addr.lastIndexOf("@");
27
+ if (at === -1 || at === addr.length - 1) return null;
28
+ return addr.slice(at + 1);
29
+ }