@m13v/seo-components 0.39.0 → 0.40.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/package.json
CHANGED
|
@@ -255,17 +255,17 @@ export function createNewsletterHandler(config: NewsletterConfig) {
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
// 4. Server-side PostHog capture (ground truth, not ad-blocked).
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
//
|
|
258
|
+
// Awaited (not `void`) so the fetch completes before the response is
|
|
259
|
+
// returned. On Cloud Run / serverless runtimes the container can be
|
|
260
|
+
// frozen the moment the response is sent, killing fire-and-forget
|
|
261
|
+
// fetches. Adds ~50-150ms latency, worth it for reliable funnel data.
|
|
262
262
|
let host: string | undefined;
|
|
263
263
|
try {
|
|
264
264
|
host = req.headers.get("host") || new URL(siteUrl).hostname;
|
|
265
265
|
} catch {
|
|
266
266
|
host = undefined;
|
|
267
267
|
}
|
|
268
|
-
|
|
268
|
+
await capturePostHogServer({
|
|
269
269
|
event: "newsletter_subscribed_server",
|
|
270
270
|
distinctId: email,
|
|
271
271
|
host,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextRequest } from "next/server";
|
|
2
|
+
import { capturePostHogServer } from "./posthog-capture";
|
|
2
3
|
|
|
3
4
|
/* ------------------------------------------------------------------ */
|
|
4
5
|
/* Resend Inbound webhook handler */
|
|
@@ -103,6 +104,26 @@ export interface ResendInboundConfig {
|
|
|
103
104
|
resendId: string;
|
|
104
105
|
timestamp: string;
|
|
105
106
|
}) => Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Optional: fire server-side PostHog events for delivery lifecycle
|
|
109
|
+
* (`email_sent_server`, `email_delivered_server`, `email_opened_server`,
|
|
110
|
+
* `email_clicked_server`, `email_bounced_server`, `email_complained_server`,
|
|
111
|
+
* `email_delayed_server`). When set, the webhook receives ALL delivery
|
|
112
|
+
* events for the connected Resend workspace, so use `fromDomainFilter` to
|
|
113
|
+
* scope to this site only.
|
|
114
|
+
*/
|
|
115
|
+
posthog?: {
|
|
116
|
+
/** Site slug attached to the PostHog event (e.g. "claude-meter"). */
|
|
117
|
+
site: string;
|
|
118
|
+
/**
|
|
119
|
+
* Only fire PostHog events when the email's `from` address ends in this
|
|
120
|
+
* domain (e.g. "claude-meter.com"). Critical because Resend webhooks are
|
|
121
|
+
* registered at workspace level: every delivery event for every domain
|
|
122
|
+
* in the account hits every webhook. Without this filter, claude-meter's
|
|
123
|
+
* PostHog gets polluted by fazm / mediar / vipassana delivery events.
|
|
124
|
+
*/
|
|
125
|
+
fromDomainFilter: string;
|
|
126
|
+
};
|
|
106
127
|
/**
|
|
107
128
|
* Skip forwarding (only persist + return 200). Useful for tests, or for
|
|
108
129
|
* sites where the DB log is the only consumer.
|
|
@@ -145,6 +166,7 @@ export function createResendInboundHandler(config: ResendInboundConfig) {
|
|
|
145
166
|
apiKeyEnv = "RESEND_API_KEY",
|
|
146
167
|
onInbound,
|
|
147
168
|
onDeliveryEvent,
|
|
169
|
+
posthog,
|
|
148
170
|
skipForward = false,
|
|
149
171
|
ignoredSenders = DEFAULT_IGNORED_SENDERS,
|
|
150
172
|
ignoredSubjects = DEFAULT_IGNORED_SUBJECTS,
|
|
@@ -220,18 +242,55 @@ export function createResendInboundHandler(config: ResendInboundConfig) {
|
|
|
220
242
|
}
|
|
221
243
|
}
|
|
222
244
|
|
|
223
|
-
async function handleDelivery(payload: ResendInboundPayload) {
|
|
245
|
+
async function handleDelivery(payload: ResendInboundPayload, host?: string) {
|
|
224
246
|
const status = DELIVERY_STATUS_MAP[payload.type];
|
|
225
|
-
if (!status
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
if (!status) return;
|
|
248
|
+
|
|
249
|
+
if (onDeliveryEvent) {
|
|
250
|
+
try {
|
|
251
|
+
await onDeliveryEvent({
|
|
252
|
+
type: payload.type,
|
|
253
|
+
status,
|
|
254
|
+
resendId: payload.data.email_id,
|
|
255
|
+
timestamp: payload.created_at,
|
|
256
|
+
});
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error(`${tag} onDeliveryEvent error`, err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Optional PostHog fire — only when configured AND the email originated
|
|
263
|
+
// from our domain (Resend webhooks are workspace-level, so filtering is
|
|
264
|
+
// mandatory to avoid cross-site pollution).
|
|
265
|
+
if (posthog) {
|
|
266
|
+
const fromAddr = parseEmail(payload.data.from || "");
|
|
267
|
+
const matchesDomain = fromAddr.endsWith(`@${posthog.fromDomainFilter.toLowerCase()}`);
|
|
268
|
+
if (!matchesDomain) {
|
|
269
|
+
console.log(
|
|
270
|
+
`${tag} posthog skip, from domain mismatch:`,
|
|
271
|
+
fromAddr,
|
|
272
|
+
"expected:",
|
|
273
|
+
posthog.fromDomainFilter,
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const recipient = payload.data.to?.[0]?.toLowerCase() || payload.data.email_id;
|
|
278
|
+
await capturePostHogServer({
|
|
279
|
+
event: `email_${status}_server`,
|
|
280
|
+
distinctId: recipient,
|
|
281
|
+
host,
|
|
282
|
+
properties: {
|
|
283
|
+
email: recipient,
|
|
284
|
+
site: posthog.site,
|
|
285
|
+
resend_email_id: payload.data.email_id,
|
|
286
|
+
type: payload.type,
|
|
287
|
+
status,
|
|
288
|
+
subject: payload.data.subject || null,
|
|
289
|
+
from: fromAddr,
|
|
290
|
+
timestamp: payload.created_at,
|
|
291
|
+
component: "createResendInboundHandler",
|
|
292
|
+
},
|
|
232
293
|
});
|
|
233
|
-
} catch (err) {
|
|
234
|
-
console.error(`${tag} onDeliveryEvent error`, err);
|
|
235
294
|
}
|
|
236
295
|
}
|
|
237
296
|
|
|
@@ -257,11 +316,18 @@ export function createResendInboundHandler(config: ResendInboundConfig) {
|
|
|
257
316
|
|
|
258
317
|
console.log(`${tag}`, payload.type, payload.data?.email_id);
|
|
259
318
|
|
|
319
|
+
let host: string | undefined;
|
|
320
|
+
try {
|
|
321
|
+
host = req.headers.get("host") || undefined;
|
|
322
|
+
} catch {
|
|
323
|
+
host = undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
260
326
|
try {
|
|
261
327
|
if (payload.type === "email.received") {
|
|
262
328
|
await handleInbound(payload, apiKey);
|
|
263
329
|
} else if (DELIVERY_STATUS_MAP[payload.type]) {
|
|
264
|
-
await handleDelivery(payload);
|
|
330
|
+
await handleDelivery(payload, host);
|
|
265
331
|
}
|
|
266
332
|
} catch (err) {
|
|
267
333
|
console.error(`${tag} handler error`, err);
|