Skip to content

Commit 82b7953

Browse files
committed
Update Resend email addy to contact.webstackbuilders.com
1 parent 447a670 commit 82b7953

10 files changed

Lines changed: 788 additions & 639 deletions

File tree

package-lock.json

Lines changed: 645 additions & 592 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@
8787
"@github/relative-time-element": "^5.0.0",
8888
"@glidejs/glide": "^3.7.1",
8989
"@googlemaps/extended-component-library": "^0.6.14",
90-
"@happy-dom/global-registrator": "^20.8.9",
90+
"@happy-dom/global-registrator": "^20.9.0",
9191
"@hubspot/api-client": "^13.5.0",
9292
"@lighthousejs/types": "^0.0.3",
9393
"@nanostores/lit": "^0.2.3",
9494
"@nanostores/persistent": "^1.3.3",
9595
"@playwright/browser-chromium": "^1.59.1",
9696
"@playwright/test": "1.59.1",
9797
"@semantic-ui/astro-lit": "^5.3.0",
98-
"@sentry/astro": "^10.47.0",
99-
"@sentry/browser": "^10.47.0",
98+
"@sentry/astro": "^10.48.0",
99+
"@sentry/browser": "^10.48.0",
100100
"@shikijs/transformers": "^4.0.2",
101101
"@tailwindcss/forms": "0.5.11",
102102
"@tailwindcss/typography": "0.5.19",
@@ -114,14 +114,14 @@
114114
"@types/hast": "^3.0.4",
115115
"@types/js-cookie": "^3.0.6",
116116
"@types/jsdom": "^28.0.1",
117-
"@types/node": "^25.5.0",
118-
"@types/nodemailer": "^7.0.11",
117+
"@types/node": "^25.6.0",
118+
"@types/nodemailer": "^8.0.0",
119119
"@types/pubsub-js": "^1.8.6",
120120
"@types/react": "^19.2.14",
121121
"@types/sanitize-html": "^2.16.1",
122122
"@types/to-ico": "1.1.3",
123-
"@typescript-eslint/eslint-plugin": "8.58.0",
124-
"@typescript-eslint/parser": "8.58.0",
123+
"@typescript-eslint/eslint-plugin": "8.58.2",
124+
"@typescript-eslint/parser": "8.58.2",
125125
"@ungap/structured-clone": "^1.3.0",
126126
"@uppy/audio": "^3.1.0",
127127
"@uppy/box": "^4.1.0",
@@ -134,61 +134,61 @@
134134
"@upstash/search": "^0.1.7",
135135
"@vercel/analytics": "^2.0.1",
136136
"@vite-pwa/astro": "^1.2.0",
137-
"@vitest/coverage-v8": "^4.1.2",
137+
"@vitest/coverage-v8": "^4.1.4",
138138
"@webcomponents/template-shadowroot": "^0.2.1",
139139
"alex": "^11.0.1",
140-
"astro": "6.1.3",
140+
"astro": "6.1.6",
141141
"astro-link-validator": "github:rodgtr1/astro-link-validator",
142142
"astro-og-canvas": "^0.11.0",
143143
"astro-vtbot": "^2.1.12",
144-
"baseline-browser-mapping": "^2.10.13",
144+
"baseline-browser-mapping": "^2.10.18",
145145
"canvas-confetti": "^1.9.4",
146146
"confusing-browser-globals": "1.0.11",
147147
"cross-env": "^10.1.0",
148148
"cross-spawn": "7.0.6",
149149
"dedent": "^1.7.2",
150-
"dotenv": "17.4.0",
150+
"dotenv": "17.4.2",
151151
"dotenv-cli": "11.0.0",
152152
"email-validator": "^2.0.4",
153153
"embla-carousel": "^8.6.0",
154154
"embla-carousel-autoplay": "^8.6.0",
155-
"eslint": "10.1.0",
155+
"eslint": "10.2.0",
156156
"eslint-import-resolver-typescript": "^4.4.4",
157-
"eslint-plugin-astro": "1.6.0",
157+
"eslint-plugin-astro": "1.7.0",
158158
"eslint-plugin-import": "2.32.0",
159159
"eslint-plugin-jsdoc": "62.9.0",
160160
"eslint-plugin-jsx-a11y": "6.10.2",
161161
"eslint-plugin-security": "4.0.0",
162162
"eslint-plugin-yml": "3.3.1",
163163
"focus-trap": "8.0.1",
164-
"globals": "^17.4.0",
165-
"gsap": "^3.14.2",
166-
"happy-dom": "^20.8.9",
164+
"globals": "^17.5.0",
165+
"gsap": "^3.15.0",
166+
"happy-dom": "^20.9.0",
167167
"hast-util-heading-rank": "^3.0.0",
168168
"hast-util-is-element": "^3.0.0",
169169
"html-element-attributes": "^3.5.0",
170170
"husky": "^9.1.7",
171171
"install": "^0.13.0",
172172
"is-whitespace-character": "^2.0.1",
173-
"isomorphic-git": "^1.37.4",
173+
"isomorphic-git": "^1.37.5",
174174
"js-cookie": "^3.0.5",
175-
"jsdom": "^29.0.1",
175+
"jsdom": "^29.0.2",
176176
"libphonenumber-js": "1.12.41",
177-
"lighthouse": "^13.0.3",
177+
"lighthouse": "^13.1.0",
178178
"lit": "^3.3.2",
179179
"markdownlint-cli2": "^0.22.0",
180180
"md-attr-parser": "^1.3.0",
181181
"mermaid": "^11.14.0",
182182
"nanostores": "^1.2.0",
183183
"node-html-parser": "^7.1.0",
184-
"nodemailer": "^8.0.4",
184+
"nodemailer": "^8.0.5",
185185
"npm": "^11.12.1",
186186
"pdf-lib": "^1.17.1",
187187
"playwright-lighthouse": "^4.0.0",
188-
"postcss": "8.5.8",
188+
"postcss": "8.5.9",
189189
"postcss-html": "1.8.1",
190-
"preact": "^10.29.0",
191-
"prettier": "3.8.1",
190+
"preact": "^10.29.1",
191+
"prettier": "3.8.2",
192192
"prettier-plugin-astro": "0.14.1",
193193
"pubsub-js": "^1.9.5",
194194
"puppeteer": "^24.40.0",
@@ -216,7 +216,7 @@
216216
"remark-stringify": "^11.0.0",
217217
"remark-supersub": "^1.0.0",
218218
"remark-video": "^0.10.0",
219-
"resend": "^6.10.0",
219+
"resend": "^6.11.0",
220220
"retext": "^9.0.0",
221221
"retext-smartypants": "^6.2.0",
222222
"rimraf": "6.1.3",
@@ -225,7 +225,7 @@
225225
"sharp": "^0.34.5",
226226
"shiki": "^4.0.2",
227227
"space-separated-tokens": "^2.0.2",
228-
"stylelint": "^17.6.0",
228+
"stylelint": "^17.7.0",
229229
"stylelint-config-standard": "^40.0.0",
230230
"stylelint-declaration-block-no-ignored-properties": "3.0.0",
231231
"stylelint-order": "8.1.1",
@@ -236,16 +236,16 @@
236236
"to-ico": "1.1.5",
237237
"tslib": "2.8.1",
238238
"typescript": "6.0.2",
239-
"typescript-eslint": "8.58.0",
239+
"typescript-eslint": "8.58.2",
240240
"unified": "^11.0.5",
241241
"unist": "^0.0.1",
242242
"unist-util-inspect": "^8.1.0",
243243
"unist-util-is": "^6.0.1",
244244
"unist-util-visit": "^5.1.0",
245245
"uuid": "^13.0.0",
246-
"vercel": "^50.38.2",
247-
"vite": "^7.3.1",
248-
"vitest": "4.1.2",
246+
"vercel": "^51.2.0",
247+
"vite": "^7.3.2",
248+
"vitest": "4.1.4",
249249
"vitest-axe": "0.1.0",
250250
"workbox-build": "7.4.0",
251251
"zod": "4.3.6"
@@ -254,6 +254,6 @@
254254
"@rollup/rollup-linux-x64-gnu": "^4.60.1"
255255
},
256256
"overrides": {
257-
"vite": "^7.3.1"
257+
"vite": "^7.3.2"
258258
}
259259
}

src/actions/contact/@types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type FileAttachment = {
2929
export type EmailData = {
3030
from: string
3131
to: string
32+
replyTo?: string
3233
subject: string
3334
html: string
3435
}

src/actions/contact/action.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
isProd,
1010
} from '@actions/utils/environment/environmentActions'
1111
import { ActionsFunctionError, handleActionsFunctionError, throwActionError } from '@actions/utils/errors'
12+
import { contactFormSender } from '@actions/utils/email/resendSenders'
1213
import { createConsentRecord } from '@actions/gdpr/entities/consent'
1314
import { createOrUpdateContact, setMarketingOptIn } from '@actions/utils/hubspot'
1415
import type { FileAttachment, EmailData } from '@actions/contact/@types'
@@ -27,16 +28,43 @@ async function sendEmail(emailData: EmailData, files: FileAttachment[]): Promise
2728
const resend = new Resend(getResendApiKey())
2829
const attachments = files.map(file => ({ filename: file.filename, content: file.content }))
2930

30-
const result = await resend.emails.send({
31-
from: emailData.from,
32-
to: emailData.to,
33-
subject: emailData.subject,
34-
html: emailData.html,
35-
...(attachments.length > 0 && { attachments }),
36-
})
31+
try {
32+
const result = await resend.emails.send({
33+
from: emailData.from,
34+
to: emailData.to,
35+
...(emailData.replyTo && { replyTo: emailData.replyTo }),
36+
subject: emailData.subject,
37+
html: emailData.html,
38+
...(attachments.length > 0 && { attachments }),
39+
})
3740

38-
if (!result.data) {
39-
throw new ActionsFunctionError('Failed to send email. Please try again later.', { status: 502 })
41+
if (result.error) {
42+
throw new ActionsFunctionError({
43+
message: `Failed to send contact email: ${result.error.message}`,
44+
appCode: 'CONTACT_EMAIL_SEND_FAILED',
45+
status: 502,
46+
route: 'actions:contact',
47+
operation: 'sendEmail',
48+
})
49+
}
50+
51+
if (!result.data) {
52+
throw new ActionsFunctionError({
53+
message: 'Failed to send contact email: Resend returned no delivery result',
54+
appCode: 'CONTACT_EMAIL_SEND_FAILED',
55+
status: 502,
56+
route: 'actions:contact',
57+
operation: 'sendEmail',
58+
})
59+
}
60+
} catch (error) {
61+
throw new ActionsFunctionError(error, {
62+
message: 'Failed to send email. Please try again later.',
63+
appCode: 'CONTACT_EMAIL_SEND_FAILED',
64+
status: 502,
65+
route: 'actions:contact',
66+
operation: 'sendEmail',
67+
})
4068
}
4169
}
4270

@@ -96,8 +124,9 @@ export const contact = {
96124
const htmlContent = generateEmailContent(formData, files)
97125
await sendEmail(
98126
{
99-
from: 'contact@webstackbuilders.com',
127+
from: contactFormSender,
100128
to: 'info@webstackbuilders.com',
129+
replyTo: formData.email.trim(),
101130
subject: `Contact Form: ${formData.name}`,
102131
html: htmlContent,
103132
},
@@ -124,13 +153,17 @@ export const contact = {
124153
message: 'Thank you for your message. We will get back to you soon!',
125154
}
126155
} catch (error) {
127-
if (error instanceof ActionsFunctionError) {
156+
if (error instanceof ActionError) {
128157
throw error
129158
}
130-
if (error instanceof ActionError) {
159+
160+
if (error instanceof ActionsFunctionError && !error.isServerError) {
131161
throw error
132162
}
133-
throwActionError(error, { route, operation: 'submit' })
163+
164+
throwActionError(error, { route, operation: 'submit' }, {
165+
fallbackMessage: 'Failed to send email. Please try again later.',
166+
})
134167
}
135168
},
136169
}),

src/actions/gdpr/entities/email.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Resend } from 'resend'
22
import { dsarVerificationEmailHtml } from '@actions/gdpr/email/dsarHtml'
33
import { dsarVerificationEmailText } from '@actions/gdpr/email/dsarText'
44
import { getResendApiKey, isProd } from '@actions/utils/environment/environmentActions'
5+
import { gdprReplyTo, gdprSender } from '@actions/utils/email/resendSenders'
56
import { getSiteUrl } from '@actions/utils/environment/siteUrlActions'
67
import { ActionsFunctionError } from '@actions/utils/errors/ActionsFunctionError'
78

@@ -61,7 +62,8 @@ export async function sendDsarVerificationEmail(
6162

6263
try {
6364
const result = await resend.emails.send({
64-
from: 'Webstack Builders <privacy@webstackbuilders.com>',
65+
from: gdprSender,
66+
replyTo: gdprReplyTo,
6567
to: email,
6668
subject: `${subject} - Webstack Builders`,
6769
html,

src/actions/newsletter/entities/email.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Resend } from 'resend'
22
import { getResendApiKey, isProd } from '@actions/utils/environment/environmentActions'
33
import { getSiteUrl } from '@actions/utils/environment/siteUrlActions'
4+
import { newsletterReplyTo, newsletterSender } from '@actions/utils/email/resendSenders'
45
import { ActionsFunctionError } from '@actions/utils/errors/ActionsFunctionError'
56
import {
67
generateConfirmationEmailHtml,
@@ -29,7 +30,8 @@ export async function sendConfirmationEmail(
2930
}
3031

3132
const resendPayload = {
32-
from: 'Webstack Builders <newsletter@webstackbuilders.com>',
33+
from: newsletterSender,
34+
replyTo: newsletterReplyTo,
3335
to: email,
3436
subject: 'Confirm your newsletter subscription - Webstack Builders',
3537
html: generateConfirmationEmailHtml(firstName, confirmUrl, expiresIn),
@@ -77,7 +79,8 @@ export async function sendWelcomeEmail(email: string, firstName?: string): Promi
7779
const resend = getResendClient()
7880

7981
const resendPayload = {
80-
from: 'Webstack Builders <newsletter@webstackbuilders.com>',
82+
from: newsletterSender,
83+
replyTo: newsletterReplyTo,
8184
to: email,
8285
subject: '🎉 Welcome to Webstack Builders!',
8386
html: generateWelcomeEmailHtml(firstName),
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const resendSendingDomain = 'contact.webstackbuilders.com'
2+
3+
export const contactFormSender = `contact@${resendSendingDomain}`
4+
export const newsletterSender = `Webstack Builders <newsletter@${resendSendingDomain}>`
5+
export const newsletterReplyTo = 'hello@webstackbuilders.com'
6+
export const gdprSender = `Webstack Builders <privacy@${resendSendingDomain}>`
7+
export const gdprReplyTo = 'privacy@webstackbuilders.com'

src/components/Pages/Contact/client/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { initEmailValidationHandler } from './email'
1616
import { applyContactPreviewState, initFormSubmission, resolveContactPreviewState } from './formSubmission'
1717
import { initGenericValidation, initNameLengthHandler, initMssgLengthHandler } from './validation'
1818
import { defineCustomElement } from '@components/scripts/utils'
19+
import { isProd } from '@components/scripts/utils/environmentClient'
1920
import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
2021
import { initUppyUpload, type UploadController } from './upload'
2122

@@ -109,7 +110,7 @@ export class ContactFormElement extends LitElement {
109110
}
110111

111112
private applyPreviewStateFromQuery(elements: ReturnType<typeof getContactFormElements>): void {
112-
if (typeof window === 'undefined' || import.meta.env.PROD) {
113+
if (typeof window === 'undefined' || isProd()) {
113114
return
114115
}
115116

src/components/scripts/sentry/__tests__/helpers.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ const createEvent = (): Parameters<typeof beforeSendHandler>[0] =>
3737
],
3838
}) as unknown as Parameters<typeof beforeSendHandler>[0]
3939

40+
const createContactSubmitHttpErrorEvent = (): Parameters<typeof beforeSendHandler>[0] =>
41+
({
42+
type: 'error',
43+
request: { url: 'https://www.webstackbuilders.com/_actions/contact.submit' },
44+
exception: {
45+
values: [
46+
{
47+
value: 'HTTP Client Error with status code: 502',
48+
mechanism: {
49+
type: 'auto.http.client.fetch',
50+
handled: false,
51+
},
52+
},
53+
],
54+
},
55+
}) as unknown as Parameters<typeof beforeSendHandler>[0]
56+
4057
const createHint = (): Parameters<typeof beforeSendHandler>[1] =>
4158
({}) as Parameters<typeof beforeSendHandler>[1]
4259

@@ -75,6 +92,17 @@ describe('sentry helpers', () => {
7592
])
7693
})
7794

95+
it('drops handled contact action http client failures', () => {
96+
isProdMock.mockReturnValue(true)
97+
getConsentSnapshotMock.mockReturnValue({ analytics: true })
98+
99+
const event = createContactSubmitHttpErrorEvent()
100+
101+
const result = beforeSendHandler(event, createHint())
102+
103+
expect(result).toBeNull()
104+
})
105+
78106
it('scrubs PII when analytics consent is missing and preserves safe breadcrumbs', () => {
79107
isProdMock.mockReturnValue(true)
80108
getConsentSnapshotMock.mockReturnValue({ analytics: false })

0 commit comments

Comments
 (0)