diff --git a/package-lock.json b/package-lock.json index a2802db..2422414 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,11 @@ "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@sveltejs/adapter-node": "^5.2.12", + "dotenv": "^16.5.0", "drizzle-orm": "^0.40.0", "lucide": "^0.513.0", + "node-fetch": "^3.3.2", + "nodemailer": "^7.0.3", "postgres": "^3.4.5" }, "devDependencies": { @@ -3012,6 +3015,15 @@ "node": ">=12" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", @@ -3091,6 +3103,18 @@ "license": "MIT", "optional": true }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/drizzle-kit": { "version": "0.30.6", "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz", @@ -3435,6 +3459,41 @@ } } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formsnap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz", @@ -4144,6 +4203,53 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -5594,6 +5700,15 @@ } } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", diff --git a/package.json b/package.json index 25fbdf8..47d4685 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,11 @@ "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@sveltejs/adapter-node": "^5.2.12", + "dotenv": "^16.5.0", "drizzle-orm": "^0.40.0", "lucide": "^0.513.0", + "node-fetch": "^3.3.2", + "nodemailer": "^7.0.3", "postgres": "^3.4.5" } } diff --git a/src/routes/sendmailtest/+page.server.js b/src/routes/sendmailtest/+page.server.js new file mode 100644 index 0000000..a50c564 --- /dev/null +++ b/src/routes/sendmailtest/+page.server.js @@ -0,0 +1,56 @@ +import { fail } from '@sveltejs/kit'; +import nodemailer from 'nodemailer'; +import { + BREVO_SMTP_HOST, + BREVO_SMTP_PORT, + BREVO_SMTP_LOGIN, + BREVO_SMTP_PASSWORD, + BREVO_SENDER_EMAIL, + BREVO_SENDER_NAME +} from '$env/static/private'; + +export const actions = { + default: async ({ request }) => { + const formData = await request.formData(); + const to = formData.get('to'); + const subject = formData.get('subject'); + const htmlContent = formData.get('htmlContent'); + + // 필수 필드 확인 + if (!to || !subject || !htmlContent) { + return fail(400, { + to: to?.toString(), + subject: subject?.toString(), + htmlContent: htmlContent?.toString(), + error: '필수 필드가 누락되었습니다: to, subject, htmlContent' + }); + } + + const transporter = nodemailer.createTransport({ + host: BREVO_SMTP_HOST, + port: parseInt(BREVO_SMTP_PORT || '587', 10), + secure: parseInt(BREVO_SMTP_PORT || '587', 10) === 465, // true for 465, false for other ports (587 uses STARTTLS) + auth: { + user: BREVO_SMTP_LOGIN, + pass: BREVO_SMTP_PASSWORD, + }, + }); + + const mailOptions = { + from: `"${BREVO_SENDER_NAME || 'Frovide'}" <${BREVO_SENDER_EMAIL}>`, + to: to.toString(), + subject: subject.toString(), + html: htmlContent.toString(), + }; + + try { + const info = await transporter.sendMail(mailOptions); + console.log('이메일 발송 성공: ' + info.messageId); + return { success: true, message: '이메일이 성공적으로 발송되었습니다.', messageId: info.messageId }; + } catch (error) { + console.error('Nodemailer 이메일 발송 오류:', error); + // @ts-ignore + return fail(500, { error: '이메일 발송에 실패했습니다.', details: error.message, to: to.toString(), subject: subject.toString(), htmlContent: htmlContent.toString() }); + } + } +}; diff --git a/src/routes/sendmailtest/+page.svelte b/src/routes/sendmailtest/+page.svelte new file mode 100644 index 0000000..25d778a --- /dev/null +++ b/src/routes/sendmailtest/+page.svelte @@ -0,0 +1,45 @@ + + +
{form.message}
+{/if} +{#if form?.error && !form?.message} +오류: {form.error}
+ {#if form?.details} +세부 정보: {form.details}
+ {/if} +{/if} + + \ No newline at end of file