Add a contact form to the resume #1

Merged
vato007 merged 18 commits from contact-form into main 2025-02-03 18:10:13 +10:30
4 changed files with 248 additions and 52 deletions
Showing only changes of commit 23f0d518c5 - Show all commits

View File

@@ -7,14 +7,39 @@
"": {
"name": "royal-leaf-c03c",
"version": "0.0.0",
"dependencies": {
"mimetext": "^3.0.27"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.6.4",
"@cloudflare/workers-types": "^4.20250129.0",
"typescript": "^5.5.2",
"vitest": "2.1.8",
"wrangler": "^3.107.2"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
"integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.7.tgz",
"integrity": "sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw==",
"dependencies": {
"core-js-pure": "^3.30.2",
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@cloudflare/kv-asset-handler": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz",
@@ -180,6 +205,7 @@
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -581,7 +607,8 @@
"optional": true,
"os": [
"android"
]
],
"peer": true
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.34.0",
@@ -594,7 +621,8 @@
"optional": true,
"os": [
"android"
]
],
"peer": true
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.34.0",
@@ -607,7 +635,8 @@
"optional": true,
"os": [
"darwin"
]
],
"peer": true
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.34.0",
@@ -620,7 +649,8 @@
"optional": true,
"os": [
"darwin"
]
],
"peer": true
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.34.0",
@@ -633,7 +663,8 @@
"optional": true,
"os": [
"freebsd"
]
],
"peer": true
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.34.0",
@@ -646,7 +677,8 @@
"optional": true,
"os": [
"freebsd"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.34.0",
@@ -659,7 +691,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.34.0",
@@ -672,7 +705,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.34.0",
@@ -685,7 +719,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.34.0",
@@ -698,7 +733,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.34.0",
@@ -711,7 +747,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.34.0",
@@ -724,7 +761,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.34.0",
@@ -737,7 +775,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.34.0",
@@ -750,7 +789,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.34.0",
@@ -763,7 +803,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.34.0",
@@ -776,7 +817,8 @@
"optional": true,
"os": [
"linux"
]
],
"peer": true
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.34.0",
@@ -789,7 +831,8 @@
"optional": true,
"os": [
"win32"
]
],
"peer": true
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.34.0",
@@ -802,7 +845,8 @@
"optional": true,
"os": [
"win32"
]
],
"peer": true
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.34.0",
@@ -815,19 +859,22 @@
"optional": true,
"os": [
"win32"
]
],
"peer": true
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/@vitest/expect": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz",
"integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/spy": "2.1.8",
"@vitest/utils": "2.1.8",
@@ -843,6 +890,7 @@
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz",
"integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/spy": "2.1.8",
"estree-walker": "^3.0.3",
@@ -869,6 +917,7 @@
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz",
"integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==",
"dev": true,
"peer": true,
"dependencies": {
"tinyrainbow": "^1.2.0"
},
@@ -881,6 +930,7 @@
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz",
"integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/utils": "2.1.8",
"pathe": "^1.1.2"
@@ -894,6 +944,7 @@
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz",
"integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/pretty-format": "2.1.8",
"magic-string": "^0.30.12",
@@ -908,6 +959,7 @@
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz",
"integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==",
"dev": true,
"peer": true,
"dependencies": {
"tinyspy": "^3.0.2"
},
@@ -920,6 +972,7 @@
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz",
"integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/pretty-format": "2.1.8",
"loupe": "^3.1.2",
@@ -967,6 +1020,7 @@
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12"
}
@@ -991,6 +1045,7 @@
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
@@ -1000,6 +1055,7 @@
"resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
"integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
"dev": true,
"peer": true,
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.1.1",
@@ -1016,6 +1072,7 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 16"
}
@@ -1041,6 +1098,16 @@
"node": ">= 0.6"
}
},
"node_modules/core-js-pure": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.40.0.tgz",
"integrity": "sha512-AtDzVIgRrmRKQai62yuSIN5vNiQjcJakJb4fbhVw3ehxx7Lohphvw9SGNWKhLFqSxC4ilD0g/L1huAYFQU3Q6A==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/data-uri-to-buffer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz",
@@ -1052,6 +1119,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -1069,6 +1137,7 @@
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
}
@@ -1089,7 +1158,8 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/esbuild": {
"version": "0.17.19",
@@ -1145,6 +1215,7 @@
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
@@ -1166,6 +1237,7 @@
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
"integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=12.0.0"
}
@@ -1200,17 +1272,24 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
"node_modules/js-base64": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz",
"integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
},
"node_modules/loupe": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
@@ -1227,6 +1306,40 @@
"node": ">=10.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimetext": {
"version": "3.0.27",
"resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.27.tgz",
"integrity": "sha512-mUhWAsZD1N/K6dbN4+a5Yq78OPnYQw1ubOSMasBntsLQ2S7KVNlvDEA8dwpr4a7PszWMzeslKahAprtwYMgaBA==",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@babel/runtime-corejs3": "^7.26.0",
"js-base64": "^3.7.7",
"mime-types": "^2.1.35"
},
"funding": {
"type": "patreon",
"url": "https://patreon.com/muratgozel"
}
},
"node_modules/miniflare": {
"version": "3.20250129.0",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250129.0.tgz",
@@ -1274,7 +1387,8 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/mustache": {
"version": "4.2.0",
@@ -1296,6 +1410,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -1326,6 +1441,7 @@
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 14.16"
}
@@ -1334,7 +1450,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/pkg-types": {
"version": "1.3.1",
@@ -1372,6 +1489,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
@@ -1387,11 +1505,17 @@
"integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==",
"dev": true
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/rollup": {
"version": "4.34.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.0.tgz",
"integrity": "sha512-+4C/cgJ9w6sudisA0nZz0+O7lTP9a3CzNLsoDwaRumM8QHwghUsu6tqHXiTmNUp/rqNiM14++7dkzHDyCRs0Jg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "1.0.6"
},
@@ -1492,7 +1616,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/source-map": {
"version": "0.6.1",
@@ -1508,6 +1633,7 @@
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1523,7 +1649,8 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/stacktracey": {
"version": "2.1.8",
@@ -1539,7 +1666,8 @@
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz",
"integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/stoppable": {
"version": "1.1.0",
@@ -1555,19 +1683,22 @@
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/tinypool": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
"integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
"dev": true,
"peer": true,
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
@@ -1577,6 +1708,7 @@
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=14.0.0"
}
@@ -1586,6 +1718,7 @@
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=14.0.0"
}
@@ -1639,6 +1772,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
"dev": true,
"peer": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
@@ -1698,6 +1832,7 @@
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz",
"integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==",
"dev": true,
"peer": true,
"dependencies": {
"cac": "^6.7.14",
"debug": "^4.3.7",
@@ -1727,6 +1862,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1743,6 +1879,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1759,6 +1896,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1775,6 +1913,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1791,6 +1930,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1807,6 +1947,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1823,6 +1964,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1839,6 +1981,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1855,6 +1998,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1871,6 +2015,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1887,6 +2032,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1903,6 +2049,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1919,6 +2066,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1935,6 +2083,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1951,6 +2100,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1967,6 +2117,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1983,6 +2134,7 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -1999,6 +2151,7 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2015,6 +2168,7 @@
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2031,6 +2185,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2047,6 +2202,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2063,6 +2219,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=12"
}
@@ -2073,6 +2230,7 @@
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -2110,6 +2268,7 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz",
"integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==",
"dev": true,
"peer": true,
"dependencies": {
"@vitest/expect": "2.1.8",
"@vitest/mocker": "2.1.8",
@@ -2175,6 +2334,7 @@
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
"dev": true,
"peer": true,
"dependencies": {
"siginfo": "^2.0.0",
"stackback": "0.0.2"

View File

@@ -13,5 +13,8 @@
"@cloudflare/workers-types": "^4.20250129.0",
"typescript": "^5.5.2",
"wrangler": "^3.107.2"
},
"dependencies": {
"mimetext": "^3.0.27"
}
}

View File

@@ -1,17 +1,52 @@
import { EmailMessage } from "cloudflare:email";
import { WorkerEntrypoint } from "cloudflare:workers";
import { createMimeMessage } from "mimetext";
const formatEmptyString = (s: string) => s ?? "Not Specified";
interface EmailDetails {
fullName: string;
organisation: string;
email: string;
mobile: string;
message: string;
}
export default class SendEmailWorker extends WorkerEntrypoint<Env> {
async fetch() {
return new Response("Unimplemented");
}
async sendEmail(rawMessage: string) {
async sendEmail({
fullName,
organisation,
email,
mobile,
message,
}: EmailDetails) {
const msg = createMimeMessage();
msg.setSender({
name: "Michael Pivato Contact Form",
addr: "contact@michaelpivato.dev",
});
msg.setRecipient("contact@michaelpivato.dev");
msg.setSubject(`Message from ${fullName ?? email}`);
msg.addMessage({
contentType: "text/plain",
data: `You've received a new message from ${fullName ?? email}.
Full Name: ${formatEmptyString(fullName)}
Organisation: ${formatEmptyString(organisation)}
Email: ${formatEmptyString(email)}
Mobile: ${formatEmptyString(mobile)}
Message:
${message}`,
});
try {
const cfMessage = new EmailMessage(
"contact@michaelpivato.dev",
"contact@michaelpivato.dev",
rawMessage
msg.asRaw()
);
await this.env.SEB.send(cfMessage);
} catch (e) {

View File

@@ -1,43 +1,41 @@
import staticFormsPlugin from "@cloudflare/pages-plugin-static-forms";
interface EmailDetails {
fullName: string;
organisation: string;
email: string;
mobile: string;
message: string;
}
interface SendEmailWorker {
sendEmail(rawMessage: string): Promise<Response>;
sendEmail(rawMessage: EmailDetails): Promise<Response>;
}
interface Env {
SERVICE: SendEmailWorker;
}
const formatEmptyString = (s: string) => s ?? "Not Specified";
export const onRequest: PagesFunction<Env> = (context) => {
// Wrap static forms plugin so we can extract the env to use email routing
return staticFormsPlugin({
respondWith: async ({ formData }) => {
const fullName = formData.get("name");
const organisation = formData.get("org") ?? "Unknown Organisation";
const organisation = formData.get("org");
const email = formData.get("email");
const mobile = formData.get("mobile") ?? "Unknown Mobile";
const mobile = formData.get("mobile");
const message = formData.get("message");
// Must have some kind of identifiable information for me to actually care about them.
if ((fullName || email) && message) {
try {
const rawEmailMessage = `----
From: Michael Pivato Contact Form <contact@michaelpivato.dev>
To: Michael Pivato <contact@michaelpivato.dev>
Subject: Message from ${fullName ?? email}
You've received a new message from ${fullName ?? email}.
Full Name: ${formatEmptyString(fullName)}
Organisation: ${formatEmptyString(organisation)}
Email: ${formatEmptyString(email)}
Mobile: ${formatEmptyString(mobile)}
Message:
${message}
----`;
await context.env.SERVICE.sendEmail(rawEmailMessage);
await context.env.SERVICE.sendEmail({
fullName,
organisation,
email,
mobile,
message,
});
} catch (e) {
return new Response(e);
}