Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
freem
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Openai/691c9bd6-70c4-8009-9802-3a9e356e3130
Add languages
Page
Discussion
English
Read
Edit
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
Edit source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
{{DISPLAYTITLE:CRM ์คํฐ๋ ์ค์ต ์ฝ๋ ๋ถ์}} ''This conversation was exported from ChatGPT''<ref>{{cite web|title=CRM ์คํฐ๋ ์ค์ต ์ฝ๋ ๋ถ์|url=https://chatgpt.com/share/691c9bd6-70c4-8009-9802-3a9e356e3130|publisher=ChatGPT|access-date=2025-11-20}}</ref> == gpt-5-1-thinking == === User: // API ํค ์ค์ Script Properties์์ ๊ฐ์ ธ์ค๊ธฐ === // API ํค ์ค์ (Script Properties์์ ๊ฐ์ ธ์ค๊ธฐ) // ์คํฌ๋ฆฝํธ ์์ฑ์ 'GEMINI_API_KEY' ์ด๋ฆ์ผ๋ก API ํค๋ฅผ ์ ์ฅํด์ผ ํฉ๋๋ค. const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'); /** * ๋ฉ๋ด๋ฅผ ์์ฑํฉ๋๋ค. (์ํธ ์ด ๋ ์๋ ์คํ) */ function onOpen() { SpreadsheetApp.getUi() .createMenu('CRM ์๋ํ') .addItem('1. ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ (C6, C7 -> C8, C9)', 'generateEmail') .addItem('2. ์ด๋ฉ์ผ ๋์์ธ ์์ฑ ๋ฐ ํ ์คํธ ๋ฐ์ก (C9, C14, C13 -> C15, ๋ฐ์ก)', 'generateEmailDesign') .addSeparator() .addItem('3. ๋จ์ฒด ์ด๋ฉ์ผ ๋ฐ์ก', 'sendBulkEmail') .addToUi(); } /** * ๋๋ฒ๊น ์ฉ: onOpen์ด ์๋ํ์ง ์์ ๋ ์๋์ผ๋ก ๋ฉ๋ด๋ฅผ ๋ง๋ญ๋๋ค. * ์ด ํจ์๋ฅผ Apps Script ํธ์ง๊ธฐ์์ ํ๋ฒ ์คํํ๋ฉด ๋ฉ๋ด๊ฐ ์์ฑ๋ฉ๋๋ค. */ function createCustomMenuManually() { SpreadsheetApp.getUi() .createMenu('CRM ์๋ํ') .addItem('1. ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ (C6, C7 -> C8, C9)', 'generateEmail') .addItem('2. ์ด๋ฉ์ผ ๋์์ธ ์์ฑ ๋ฐ ํ ์คํธ ๋ฐ์ก (C9, C14, C13 -> C15, ๋ฐ์ก)', 'generateEmailDesign') .addSeparator() .addItem('3. ๋จ์ฒด ์ด๋ฉ์ผ ๋ฐ์ก', 'sendBulkEmail') .addToUi(); } /** * C6(ํต์ฌ๋ด์ฉ)๊ณผ C7(๋ด์ฉ ํ๋กฌํํธ ๋ ์ค)์ ๊ธฐ๋ฐ์ผ๋ก * Gemini๋ฅผ ํธ์ถํ์ฌ C8(์ ๋ชฉ), C9(๋ณธ๋ฌธ)์ ์์ฑํฉ๋๋ค. */ function generateEmail() { // ... (๊ธฐ์กด ์ฝ๋์ ๋์ผ) const sheet = SpreadsheetApp.getActiveSheet(); const ss = SpreadsheetApp.getActiveSpreadsheet(); // C6: ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉ const emailCore = sheet.getRange('C6').getValue(); // C7: ์ค๋งํธ์นฉ์์ ๊ตฌ๊ธ ๋ ์ค URL ๊ฐ์ ธ์ค๊ธฐ (Sheets API ์ฌ์ฉ) const sheetName = sheet.getName(); const a1Notation = '${sheetName}'!C7; let obj; try { obj = Sheets.Spreadsheets.get(ss.getId(), { ranges: [a1Notation], fields: "sheets(data(rowData(values(userEnteredValue,chipRuns))))" }); } catch(e) { Logger.log('Sheets API ํธ์ถ ์๋ฌ: ' + e.toString()); SpreadsheetApp.getUi().alert('Google Sheets API ๊ณ ๊ธ ์๋น์ค๊ฐ ํ์ฑํ๋์ด ์๋์ง ํ์ธํ์ธ์. ์๋ฌ: ' + e.toString()); return; } // chipRuns์์ URL ์ถ์ถ let docUrl = null; try { const rowData = obj.sheets[0].data[0].rowData[0]; const chipRuns = rowData.values[0].chipRuns; if (chipRuns && chipRuns.length > 0) { for (const run of chipRuns) { if (run.chip && run.chip.richLinkProperties && run.chip.richLinkProperties.uri) { docUrl = run.chip.richLinkProperties.uri; break; } } } } catch (e) { Logger.log('C7 chipRuns ํ์ฑ ์๋ฌ: ' + e.toString()); // ์นฉ์ด ์๊ฑฐ๋ ํ์ฑ ์คํจ ์ docUrl์ null๋ก ์ ์ง๋ฉ๋๋ค. } // ์ ๋ ฅ๊ฐ ์ฒดํฌ if (!emailCore) { SpreadsheetApp.getUi().alert('C6์ ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉ์ ์ ๋ ฅํด์ฃผ์ธ์!'); return; } if (!docUrl) { SpreadsheetApp.getUi().alert('C7์ ๊ตฌ๊ธ ๋ ์ค ์ค๋งํธ์นฉ์ด ์์ต๋๋ค!'); return; } // ๊ตฌ๊ธ ๋ ์ค์์ ํ๋กฌํํธ ๊ฐ์ ธ์ค๊ธฐ const userPrompt = getDocContent(docUrl); if (!userPrompt) { SpreadsheetApp.getUi().alert('C7์ ๊ตฌ๊ธ ๋ ์ค์์ ํ๋กฌํํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค! (๋ฌธ์ ์ ๊ทผ ๊ถํ ๋ฑ ํ์ธ)'); return; } // Gemini์๊ฒ ๋ณด๋ผ ์ต์ข ํ๋กฌํํธ ๊ตฌ์ฑ const finalPrompt = ${userPrompt} ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉ: ${emailCore} ๋ค์ ํ์์ผ๋ก ์ ํํ๊ฒ ์๋ตํด์ฃผ์ธ์: ์ ๋ชฉ: [์ฌ๊ธฐ์ ์ด๋ฉ์ผ ์ ๋ชฉ] --- [์ฌ๊ธฐ์ ์ด๋ฉ์ผ ๋ณธ๋ฌธ]; // Gemini API ํธ์ถ const payload = { contents: [ { parts: [ { text: finalPrompt }, ], }, ], }; const url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'; const options = { method: 'POST', contentType: 'application/json', headers: { 'x-goog-api-key': apiKey, }, payload: JSON.stringify(payload) }; try { const response = UrlFetchApp.fetch(url, options); const data = JSON.parse(response.getContentText()); const content = data['candidates'][0]['content']['parts'][0]['text']; // ์๋ต ํ์ฑ (์ ๋ชฉ๊ณผ ๋ณธ๋ฌธ ๋ถ๋ฆฌ) // ... (๊ธฐ์กด ์ฝ๋์ ๋์ผ) const lines = content.split('\n'); let subject = ''; let body = ''; let foundSeparator = false; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('์ ๋ชฉ:')) { subject = line.replace('์ ๋ชฉ:', '').trim(); } else if (line === '---') { foundSeparator = true; } else if (foundSeparator) { body += (body ? '\n' : '') + lines[i]; } } // C8์ ์ ๋ชฉ, C9์ ๋ณธ๋ฌธ ์ ๋ ฅ sheet.getRange('C8').setValue(subject.trim()); sheet.getRange('C9').setValue(body.trim()); SpreadsheetApp.getUi().alert('์ด๋ฉ์ผ ๋ด์ฉ์ด ์์ฑ๋์์ต๋๋ค! (C8, C9)'); } catch (error) { Logger.log('Gemini API ์๋ฌ: ' + error.toString()); SpreadsheetApp.getUi().alert('๋ด์ฉ ์์ฑ ์ค ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.toString()); } } /** * C9(์ด๋ฉ์ผ ๋ณธ๋ฌธ), C14(๋์์ธ ํ๋กฌํํธ ๋ ์ค)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก C15(HTML ๋์์ธ)๋ฅผ ์์ฑํ๊ณ , * C13์ ์ ๋ ฅ๋ ํ ์คํธ ์ฃผ์๋ก ๋ฉ์ผ์ ๋ฐ์กํฉ๋๋ค. */ function generateEmailDesign() { const sheet = SpreadsheetApp.getActiveSheet(); const ss = SpreadsheetApp.getActiveSpreadsheet(); const ui = SpreadsheetApp.getUi(); // 1. C9์์ ์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ๊ฐ์ ธ์ค๊ธฐ const emailText = sheet.getRange('C9').getValue(); // 2. C14์์ ์ค๋งํธ์นฉ ๊ตฌ๊ธ ๋ ์ค URL ๊ฐ์ ธ์ค๊ธฐ (Sheets API ์ฌ์ฉ) const sheetName = sheet.getName(); const a1Notation = '${sheetName}'!C14; let obj; try { obj = Sheets.Spreadsheets.get(ss.getId(), { ranges: [a1Notation], fields: "sheets(data(rowData(values(userEnteredValue,chipRuns))))" }); } catch(e) { Logger.log('Sheets API ํธ์ถ ์๋ฌ: ' + e.toString()); ui.alert('Google Sheets API ๊ณ ๊ธ ์๋น์ค๊ฐ ํ์ฑํ๋์ด ์๋์ง ํ์ธํ์ธ์. ์๋ฌ: ' + e.toString()); return; } // chipRuns์์ URL ์ถ์ถ let designPromptDocUrl = null; try { const rowData = obj.sheets[0].data[0].rowData[0]; const chipRuns = rowData.values[0].chipRuns; if (chipRuns && chipRuns.length > 0) { for (const run of chipRuns) { if (run.chip && run.chip.richLinkProperties && run.chip.richLinkProperties.uri) { designPromptDocUrl = run.chip.richLinkProperties.uri; break; } } } } catch (e) { Logger.log('C14 chipRuns ํ์ฑ ์๋ฌ: ' + e.toString()); } // 3. ์ ๋ ฅ๊ฐ ์ฒดํฌ if (!emailText) { ui.alert('C9์ ์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ์ด ์์ต๋๋ค. ๋จผ์ ๋ด์ฉ์ ์์ฑํด์ฃผ์ธ์!'); return; } if (!designPromptDocUrl) { ui.alert('C14์ ๋์์ธ ํ๋กฌํํธ๊ฐ ๋ด๊ธด ๊ตฌ๊ธ ๋ ์ค ์ค๋งํธ์นฉ์ ์ฐ๊ฒฐํด์ฃผ์ธ์!'); return; } // 4. ๊ตฌ๊ธ ๋ ์ค์์ ๋์์ธ ํ๋กฌํํธ ๊ฐ์ ธ์ค๊ธฐ const designPrompt = getDocContent(designPromptDocUrl); if (!designPrompt) { ui.alert('C14์ ๊ตฌ๊ธ ๋ ์ค์์ ํ๋กฌํํธ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค! (๋ฌธ์ ์ ๊ทผ ๊ถํ ๋ฑ ํ์ธ)'); return; } // 5. Gemini์๊ฒ ๋ณด๋ผ ์ต์ข ํ๋กฌํํธ ๊ตฌ์ฑ const finalPrompt = ๋น์ ์ ์ ๋ฌธ ์ด๋ฉ์ผ HTML ๋์์ด๋์ ๋๋ค. ๋ค์์ ๋์์ด๋์๊ฒ ์ ํ๋ ๋์์ธ ์ง์นจ(ํ๋กฌํํธ)์ ๋๋ค: ---[๋์์ธ ์ง์นจ ์์]--- ${designPrompt} ---[๋์์ธ ์ง์นจ ๋]--- ๋ค์์ ์ด๋ฉ์ผ์ ๋ค์ด๊ฐ ์ค์ ํ ์คํธ ๋ด์ฉ์ ๋๋ค: ---[์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ์์]--- ${emailText} ---[์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ๋]--- [์์ฒญ ์ฌํญ] ๋์์ธ ์ง์นจ์ ๋ฐ๋ผ, ์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ์ ์ฌ์ฉํ์ฌ ์์ ํ ์ด๋ฉ์ผ HTML ์ฝ๋๋ฅผ ์์ฑํด์ฃผ์ธ์. * ๋ชจ๋ ์คํ์ผ์ ๋ฐ๋์ ์ธ๋ผ์ธ(inline)์ผ๋ก ์ ์ฉํด์ผ ํฉ๋๋ค (Gmail ๋ฑ์์์ ํธํ์ฑ ๋ณด์ฅ). * ๋ฐ์ํ ๋์์ธ์ ๊ณ ๋ คํ์ฌ <style> ํ๊ทธ ๋ด์ ๋ฏธ๋์ด ์ฟผ๋ฆฌ๋ฅผ ํฌํจํ ์ ์์ต๋๋ค. * ๋ค๋ฅธ ์ค๋ช ์ด๋ ์ฃผ์ ์์ด, ์์ํ HTML ์ฝ๋(<html>...</html>)๋ง ์๋ต์ผ๋ก ๋ฐํํด์ฃผ์ธ์. * ํ ์คํธ ์ธ์ฝ๋ฉ์ UTF-8๋ก ์ค์ ํด์ฃผ์ธ์. (์: <meta charset="UTF-8">) ; // 6. Gemini API ํธ์ถ const payload = { contents: [ { parts: [ { text: finalPrompt }, ], }, ], }; const url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'; const options = { method: 'POST', contentType: 'application/json', headers: { 'x-goog-api-key': apiKey, }, payload: JSON.stringify(payload) }; let htmlResult = ''; try { const response = UrlFetchApp.fetch(url, options); const data = JSON.parse(response.getContentText()); const content = data['candidates'][0]['content']['parts'][0]['text']; // ์๋ต ์ ๋ฆฌ htmlResult = content.trim(); if (htmlResult.startsWith('html')) { htmlResult = htmlResult.substring(7); } if (htmlResult.endsWith('')) { htmlResult = htmlResult.substring(0, htmlResult.length - 3); } htmlResult = htmlResult.trim(); // 7. C15์ ๊ฒฐ๊ณผ(HTML ์ฝ๋) ์ ๋ ฅ sheet.getRange('C15').setValue(htmlResult); } catch (error) { Logger.log('Gemini API ์๋ฌ (๋์์ธ): ' + error.toString()); ui.alert('๋์์ธ ์์ฑ ์ค ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.toString()); return; } // --- [์ถ๊ฐ๋ ๋ก์ง: ํ ์คํธ ๋ฉ์ผ ๋ฐ์ก] --- const testEmail = sheet.getRange('C13').getValue().trim(); // C13์์ ํ ์คํธ ์ฃผ์ ๊ฐ์ ธ์ค๊ธฐ const subject = sheet.getRange('C8').getValue(); // C8์์ ์ ๋ชฉ ๊ฐ์ ธ์ค๊ธฐ if (testEmail) { Logger.log(Sending test email to: ${testEmail}); const emailOptions = { htmlBody: htmlResult, // C15์ ์ ์ฅ๋ HTML ๋ด์ฉ name: 'AI CRM ์์คํ (ํ ์คํธ)', // ๋ฐ์ ์ ์ด๋ฆ (ํ ์คํธ์ฉ) }; try { // ์ ๋ชฉ์ [ํ ์คํธ] prefix ์ถ๊ฐ GmailApp.sendEmail(testEmail, [ํ ์คํธ] ${subject}, "", emailOptions); // ์ต์ข ์ฑ๊ณต ์๋ฆผ (์ ์ฅ + ๋ฐ์ก) ui.alert(์ด๋ฉ์ผ ๋์์ธ์ด ์์ฑ๋์ด C15์ ์ ๋ ฅ๋์์ผ๋ฉฐ, ํ ์คํธ ๋ฉ์ผ์ด ${testEmail}๋ก ๋ฐ์ก๋์์ต๋๋ค!); } catch (sendError) { Logger.log('Test Email Send Error: ' + sendError.toString()); // HTML ์ ์ฅ์ ์ฑ๊ณตํ์ผ๋ฏ๋ก, ๋ฐ์ก ์คํจ๋ง ์๋ฆผ ui.alert([๊ฒฝ๊ณ ] ์ด๋ฉ์ผ ๋์์ธ์ C15์ ์ ์ฅ๋์์ผ๋, ํ ์คํธ ๋ฉ์ผ ๋ฐ์ก์ ์คํจํ์ต๋๋ค. (์ฃผ์/๊ถํ ํ์ธ) ์๋ฌ: ${sendError.toString()}); } } else { // C13์ด ๋น์ด์๋ ๊ฒฝ์ฐ ui.alert('์ด๋ฉ์ผ ๋์์ธ์ด ์์ฑ๋์ด C15์ ์ ๋ ฅ๋์์ต๋๋ค! (C13์ ํ ์คํธ ์ฃผ์๊ฐ ์์ด ๋ฐ์ก๋์ง ์์์ต๋๋ค)'); } } /** * [NEW] B19:C24 ์ค์ ๋ฐ ์ฃผ์๋ก์ ๊ธฐ๋ฐ์ผ๋ก ๋จ์ฒด ๋ฉ์ผ์ ๋ฐ์กํฉ๋๋ค. */ function sendBulkEmail() { const ui = SpreadsheetApp.getUi(); const sheet = SpreadsheetApp.getActiveSheet(); const ss = SpreadsheetApp.getActiveSpreadsheet(); try { // --- 1. ๋ฐ์ก ๊ธฐ๋ณธ ์ ๋ณด ๋ก๋ --- const subject = sheet.getRange('C8').getValue(); const htmlBodyTemplate = sheet.getRange('C15').getValue(); // ์๋ณธ ํ ํ๋ฆฟ const senderEmail = sheet.getRange('C19').getValue().trim(); const senderName = sheet.getRange('C20').getValue().trim(); // --- 2. ์ฃผ์๋ก(์ค๋งํธ์นฉ) ์ ๋ณด ๋ก๋ --- const addressBookSheetName = sheet.getRange('C22').getValue().trim(); // C21์์ ์ค๋งํธ์นฉ URL ๊ฐ์ ธ์ค๊ธฐ const sheetName = sheet.getName(); const a1Notation = '${sheetName}'!C21; // C21 let obj; try { obj = Sheets.Spreadsheets.get(ss.getId(), { ranges: [a1Notation], fields: "sheets(data(rowData(values(userEnteredValue,chipRuns))))" }); } catch (e) { Logger.log('Sheets API ํธ์ถ ์๋ฌ: ' + e.toString()); ui.alert('Google Sheets API ๊ณ ๊ธ ์๋น์ค๊ฐ ํ์ฑํ๋์ด ์๋์ง ํ์ธํ์ธ์. ์๋ฌ: ' + e.toString()); return; } let addressBookSheetUrl = null; try { const rowData = obj.sheets[0].data[0].rowData[0]; const chipRuns = rowData.values[0].chipRuns; if (chipRuns && chipRuns.length > 0) { for (const run of chipRuns) { if (run.chip && run.chip.richLinkProperties && run.chip.richLinkProperties.uri) { addressBookSheetUrl = run.chip.richLinkProperties.uri; break; } } } } catch (e) { Logger.log('C21 chipRuns ํ์ฑ ์๋ฌ: ' + e.toString()); } // --- 3. ์ฃผ์๋ก ๋ฐ์ดํฐ ๋งคํ --- const emailColumnLetter = sheet.getRange('C23').getValue().trim().toUpperCase(); const nameColumnLetter = sheet.getRange('C24').getValue().trim().toUpperCase(); // --- ํ์ ์ ๋ณด ์ฒดํฌ --- if (!subject || !htmlBodyTemplate) { ui.alert('C8(์ ๋ชฉ)๊ณผ C15(HTML ๋์์ธ)์ ๋ด์ฉ์ด ์๋์ง ํ์ธํด์ฃผ์ธ์.'); return; } if (!addressBookSheetUrl || !addressBookSheetName || !emailColumnLetter || !nameColumnLetter) { ui.alert('C21:C24์ ์ฃผ์๋ก ์ค์ ๊ฐ(์ค๋งํธ์นฉ, ์ํธ๋ช , ์ด์ด๋ฆ)์ ๋ชจ๋ ์ ๋ ฅํด์ฃผ์ธ์.'); return; } if (!senderName) { ui.alert('C20(๋ฐ์ ์ ์ด๋ฆ)์ ์ ๋ ฅํด์ฃผ์ธ์.'); return; } // --- 4. ์ฃผ์๋ก ๋ฐ์ดํฐ ์ ์ฒด ๋ก๋ --- const addressBookSheetId = getDocIdFromUrl(addressBookSheetUrl); // ํฌํผ ํจ์ ์ฌ์ฌ์ฉ if (!addressBookSheetId) { ui.alert('C21์ ์ฃผ์๋ก ์ํธ URL์ด ์ ํจํ์ง ์์ต๋๋ค.'); return; } const addressBookSpreadsheet = SpreadsheetApp.openById(addressBookSheetId); const addressBookSheet = addressBookSpreadsheet.getSheetByName(addressBookSheetName); if (!addressBookSheet) { ui.alert(์ฐ๊ฒฐ๋ ์ฃผ์๋ก ์ํธ์์ '${addressBookSheetName}' ์ํธ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.); return; } const data = addressBookSheet.getDataRange().getValues(); // ์ด ๋ฌธ์๋ฅผ 0-based ์ธ๋ฑ์ค๋ก ๋ณํ const emailColIndex = columnLetterToIndex(emailColumnLetter); const nameColIndex = columnLetterToIndex(nameColumnLetter); if (emailColIndex === -1 || nameColIndex === -1) { ui.alert('์ด๋ฉ์ผ ๋๋ ์ด๋ฆ ์ด์ด๋ฆ์ด ์ ํจํ์ง ์์ต๋๋ค (์: A, B, C...).'); return; } // --- 5. ๊ฐ์ธํ ๋ฐ ์์ฐจ ๋ฐ์ก (Loop) --- let sentCount = 0; const placeholder = /{{์ด๋ฆ}}/g; // ์นํ ํ๊ทธ (์ ๊ท์, global replace) // GmailApp ์ต์ const options = { name: senderName, // ๋ฐ์ ์ ์ด๋ฆ htmlBody: null, // ๋ฃจํ ์์์ ์ฑ์์ง }; if (senderEmail) { options.from = senderEmail; // ๋ฐ์ ์ ์ด๋ฉ์ผ (๋ณ์นญ ๋ฑ) } // ์ฒซ ๋ฒ์งธ ์ค(ํค๋)์ ๊ฑด๋๋ฐ๊ณ (i = 1) ์์ for (let i = 1; i < data.length; i++) { const row = data[i]; const recipientEmail = row[emailColIndex]; const recipientName = row[nameColIndex]; // ์ด๋ฉ์ผ ์ฃผ์๊ฐ ์๊ฑฐ๋ ์ ํจํ์ง ์์ผ๋ฉด ๊ฑด๋๋ฐ๊ธฐ if (!recipientEmail || recipientEmail.indexOf('@') === -1) { Logger.log(Skipped row ${i + 1}: Invalid email.); continue; } // ์ด๋ฆ์ด ์์ ๊ฒฝ์ฐ๋ฅผ ๋๋น (์: "๊ณ ๊ฐ"๋) const nameForEmail = recipientName || "๊ณ ๊ฐ"; // ๊ฐ์ธํ๋ HTML ๋ณธ๋ฌธ ์์ฑ const personalizedBody = htmlBodyTemplate.replace(placeholder, nameForEmail); options.htmlBody = personalizedBody; // ์ด๋ฉ์ผ ๋ฐ์ก try { GmailApp.sendEmail(recipientEmail, subject, "", options); // 3๋ฒ์งธ ์ธ์(์ผ๋ฐ ํ ์คํธ ๋ณธ๋ฌธ)๋ ๋น์๋ Logger.log(Sent to: ${recipientEmail}); sentCount++; // Google Quota ์ ์ฑ ์ ์ํ 1์ด ๋๊ธฐ Utilities.sleep(1000); } catch (e) { Logger.log(๋ฐ์ก ์คํจ (${recipientEmail}): ${e.toString()}); ui.alert(๋ฐ์ก ์ค๋จ: ${recipientEmail}๋์๊ฒ ๋ฐ์ก ์คํจ. \n์๋ฌ: ${e.toString()}); return; // ํ ๊ฑด์ด๋ผ๋ ์คํจํ๋ฉด ์ค๋จ } } // --- 6. ๊ฒฐ๊ณผ ์๋ฆผ --- ui.alert(์ด ${sentCount}๊ฑด์ ์ด๋ฉ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์ก๋์์ต๋๋ค.); } catch (error) { Logger.log('sendBulkEmail ์๋ฌ: ' + error.toString()); ui.alert('๋จ์ฒด ๋ฐ์ก ์ค ์ ์ ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค: ' + error.toString()); } } // --- ํฌํผ ํจ์ (Helper Functions) --- /** * ๊ตฌ๊ธ ๋ ์ค URL์์ ๋ฌธ์ ID๋ฅผ ์ถ์ถํฉ๋๋ค. * (Docs์ Sheets URL ๋ชจ๋์์ ID ์ถ์ถ ๊ฐ๋ฅ) * @param {string} url - ๊ตฌ๊ธ ๋ ์ค/์ํธ URL * @return {string|null} - ์ถ์ถ๋ ๋ฌธ์ ID ๋๋ null */ function getDocIdFromUrl(url) { // ์ ๊ท์์ผ๋ก URL์์ ID ์ถ์ถ const match = url.match(/[-\w]{25,}/); return match ? match[0] : null; } /** * ๊ตฌ๊ธ ๋ ์ค URL์ ๋ฐ์ ํด๋น ๋ฌธ์์ ํ ์คํธ ๋ด์ฉ์ ๋ฐํํฉ๋๋ค. * @param {string} docUrl - ๊ตฌ๊ธ ๋ ์ค URL (์ค๋งํธ์นฉ์์ ์ถ์ถ) * @return {string|null} - ๋ฌธ์ ํ ์คํธ ๋ด์ฉ ๋๋ null (์๋ฌ ์) */ function getDocContent(docUrl) { try { const docId = getDocIdFromUrl(docUrl); if (!docId) { Logger.log('์ ํจํ์ง ์์ ๋ ์ค URL์ ๋๋ค: ' + docUrl); return null; } // DocumentApp์ ์ฌ์ฉํ์ฌ ๋ฌธ์ ์ด๊ธฐ const doc = DocumentApp.openById(docId); const body = doc.getBody(); const text = body.getText(); return text; } catch (error) { Logger.log('๋ ์ค๋ฅผ ์ฝ๋ ์ค ์๋ฌ ๋ฐ์: ' + error.toString()); return null; } } /** * [NEW] ์คํ๋ ๋์ํธ ์ด ๋ฌธ์(A, B, C...)๋ฅผ 0-based ์ธ๋ฑ์ค๋ก ๋ณํํฉ๋๋ค. * @param {string} letter - ์ด ๋ฌธ์ (์: "A", "C", "AA") * @return {number} - 0-based ์ธ๋ฑ์ค (์: "A" -> 0, "C" -> 2) */ function columnLetterToIndex(letter) { try { let column = 0, length = letter.length; for (let i = 0; i < length; i++) { column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1); } return column - 1; // 0-based index } catch (e) { Logger.log(columnLetterToIndex ์๋ฌ: ${e.toString()}); return -1; // ์๋ฌ ์ -1 ๋ฐํ } } --- ์๋ ์ฑ์ค์คํฌ๋ฆฝํธ ์ฝ๋์ผ. ๊ฐ๋ฐ ์ ๋ชจ๋ฅด๋ ์ด๋ณด์ํํ ์ด ์์คํ ์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ์ค๋ช ํ๊ณ , ์ฝ๋์ ๊ฐ ๋ถ๋ถ์ด ์ด๋ค ์ญํ ์ ํ๋์ง ๊ฐ๋จํ ๋ฆฌ๋ทฐํ๋ฉด์ ์๊ฐํด์ฃผ๋ ค๊ณ ํด. ์ฝ๋ ๋ถ์ํด์ ์งํํด์ค. ํ์ํ ์ ๋ณด ์์ผ๋ฉด ๋์๊ฒ ๋ฌผ์ด๋ด. === Assistant: ์๋ ์ค๋ช ์ โ๊ฐ๋ฐ ์ ๋ชจ๋ฅด๋ ์ด๋ณด์โ์๊ฒ ๋งํด์ค๋ค๋ ์ ์ ๋ก ์ผ์ต๋๋ค. === ์๋ ์ค๋ช ์ โ๊ฐ๋ฐ ์ ๋ชจ๋ฅด๋ ์ด๋ณด์โ์๊ฒ ๋งํด์ค๋ค๋ ์ ์ ๋ก ์ผ์ต๋๋ค. ๋จผ์ ์์คํ ์ ์ฒด ํ๋ฆ์ ์ฌ๋ ๋๋์ด์์ ์ ๋ฆฌํ๊ณ , ๊ทธ๋ค์ ์ฝ๋ ๋ธ๋ก๋ณ๋ก ์ญํ ์ ์งง๊ฒ ๋ฆฌ๋ทฐํฉ๋๋ค. ==== 1. ์ด ์คํฌ๋ฆฝํธ๋ก ์ค์ ๋ก ํ ์ ์๋ ์ผ (์ฌ์ฉ์ ๊ด์ ) ==== ๊ตฌ๊ธ ์คํ๋ ๋์ํธ์์ ๋ฉ๋ด์ โCRM ์๋ํโ๊ฐ ์๊ธฐ๊ณ , ๊ฑฐ๊ธฐ์ 3๊ฐ์ง ๊ธฐ๋ฅ์ด ์๊น๋๋ค. # ์ด๋ฉ์ผ ๋ด์ฉ ์๋ ์์ฑ (๋ฉ๋ด 1๋ฒ) - C6: โ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉโ์ ์ ์ด๋ - C7: โ๋ด์ฉ ํ๋กฌํํธ๊ฐ ์ ํ ๊ตฌ๊ธ ๋ ์ค ๋ฌธ์โ๋ฅผ ์ค๋งํธ์นฉ์ผ๋ก ์ฐ๊ฒฐ โ ๋ฒํผ์ ๋๋ฅด๋ฉด, - Gemini๊ฐ C6 + ๋ ์ค ๋ด์ฉ์ ๋ณด๊ณ - C8: ์ด๋ฉ์ผ ์ ๋ชฉ - C9: ์ด๋ฉ์ผ ๋ณธ๋ฌธ ์ ์๋์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค. # ์ด๋ฉ์ผ ๋์์ธ(HTML) ์์ฑ + ํ ์คํธ ๋ฐ์ก (๋ฉ๋ด 2๋ฒ) - C9: ๋ฐฉ๊ธ ๋ง๋ ์ด๋ฉ์ผ ํ ์คํธ - C14: โ๋์์ธ ์ง์นจ์ด ์ ํ ๊ตฌ๊ธ ๋ ์ค ๋ฌธ์โ ์ค๋งํธ์นฉ - C13: ํ ์คํธ๋ก ๋ฐ์ ๋ด ์ด๋ฉ์ผ ์ฃผ์ โ ๋ฒํผ์ ๋๋ฅด๋ฉด, - Gemini๊ฐ ํ ์คํธ + ๋์์ธ ์ง์นจ์ ๋ณด๊ณ HTML ์ด๋ฉ์ผ ๋์์ธ์ ์์ฑํด์ C15์ ์ ์ฅ - C13์ ์ ํ ์ฃผ์๋ก [ํ ์คํธ] ์ ๋ชฉ์ผ๋ก ์ค์ ์ด๋ฉ์ผ ๋ฐ์ก # ๋จ์ฒด ์ด๋ฉ์ผ ๋ฐ์ก (๋ฉ๋ด 3๋ฒ) - C8: ์ต์ข ์ด๋ฉ์ผ ์ ๋ชฉ - C15: ์ต์ข HTML ์ด๋ฉ์ผ (๋์์ธ ํฌํจ) - C19~C24: โ์ด๋ค ์ฃผ์๋ก ์ํธ, ์ด๋ค ์ด์ ์ด๋ฉ์ผ/์ด๋ฆ์ด ์๋์งโ ์ค์ โ ๋ฒํผ์ ๋๋ฅด๋ฉด, - ์ฃผ์๋ก ์คํ๋ ๋์ํธ๋ฅผ ์ด๊ณ - ๊ฐ ์ฌ๋์ ์ด๋ฆ๊ณผ ์ด๋ฉ์ผ์ ์ฝ์ด์ - C15 ํ ํ๋ฆฟ์์ {{์ด๋ฆ}}์ ๊ฐ ์ฌ๋ ์ด๋ฆ์ผ๋ก ๋ฐ๊ฟ ๋ฃ๊ณ - Gmail๋ก ํ ๋ช ์ฉ ์์ฐจ ๋ฐ์ก ์ฆ, '''โํต์ฌ ๋ด์ฉ โ ๋ฌธ์ฅํ โ ๋์์ธ๋ HTML ์ด๋ฉ์ผ โ ๊ฐ์ธํ๋ ๋จ์ฒด ๋ฐ์กโ'''๊น์ง ํ ๋ฒ์ ์ด์ด์ง๋ ํ์ดํ๋ผ์ธ์ ๋๋ค. ==== 2. ์ฝ๋ ์ ์ฒด ๊ตฌ์กฐ ์ง๋ ==== ์ฝ๋๋ ํฌ๊ฒ 6 ๋ฉ์ด๋ฆฌ์ ๋๋ค. # ์๋จ: API ํค ์ฝ๊ธฐ # ๋ฉ๋ด ์์ฑ: onOpen, createCustomMenuManually # ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ: generateEmail # ์ด๋ฉ์ผ ๋์์ธ ์์ฑ + ํ ์คํธ ๋ฐ์ก: generateEmailDesign # ๋จ์ฒด ๋ฉ์ผ ๋ฐ์ก: sendBulkEmail # ํฌํผ ํจ์: getDocIdFromUrl, getDocContent, columnLetterToIndex ์ด์ ๊ฐ ํจ์๊ฐ ํ๋ ์ผ์ ์ด๋ณด์ ๊ธฐ์ค์ผ๋ก ๊ฐ๋จํ ํ์ด๋ณด๊ฒ ์ต๋๋ค. ==== 3. ์๋จ: API ํค ์ค์ ==== <syntaxhighlight lang="js">const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'); </syntaxhighlight> * ์คํฌ๋ฆฝํธ ์์ฑ์ ์ ์ฅํด ๋ GEMINI_API_KEY ๊ฐ์ ๋ถ๋ฌ์์, Gemini API๋ฅผ ํธ์ถํ ๋ ์ฌ์ฉํ ํค๋ฅผ ์ ์ญ ๋ณ์๋ก ๋ค๊ณ ์์ต๋๋ค. * ์ฅ์ : ์ฝ๋์ ํค๋ฅผ ์ง์ ์ ์ง ์์์ ๋ณด์์ ๋ ์์ ํจ. ==== 4. ๋ฉ๋ด ์์ฑ: onOpen, createCustomMenuManually ==== <syntaxhighlight lang="js">function onOpen() { SpreadsheetApp.getUi() .createMenu('CRM ์๋ํ') .addItem('1. ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ...', 'generateEmail') .addItem('2. ์ด๋ฉ์ผ ๋์์ธ ์์ฑ ๋ฐ ํ ์คํธ ๋ฐ์ก...', 'generateEmailDesign') .addSeparator() .addItem('3. ๋จ์ฒด ์ด๋ฉ์ผ ๋ฐ์ก', 'sendBulkEmail') .addToUi(); } </syntaxhighlight> * ์ํธ๋ฅผ ์ด ๋ ์๋์ผ๋ก ์คํ๋จ. * ์๋จ ๋ฉ๋ด์ โCRM ์๋ํโ๋ผ๋ ์ปค์คํ ๋ฉ๋ด๋ฅผ ๋ง๋ค๊ณ , ๊ฐ ํญ๋ชฉ์ ํจ์ ์ด๋ฆ์ ์ฐ๊ฒฐ: - ๋ฉ๋ด 1 โ generateEmail - ๋ฉ๋ด 2 โ generateEmailDesign - ๋ฉ๋ด 3 โ sendBulkEmail <syntaxhighlight lang="js">function createCustomMenuManually() { ... } </syntaxhighlight> * onOpen์ด ์ ์ ๋์ํ ๋, ์ฑ์ค ์คํฌ๋ฆฝํธ ํธ์ง๊ธฐ์์ ์ด ํจ์๋ฅผ ํ ๋ฒ ์๋ ์คํํ๋ฉด ๋์ผํ ๋ฉ๋ด๋ฅผ ๋ง๋ค์ด ์ฃผ๋ โ๋น์์ฉ ๋ฒํผโ์ ๋๋ค. ==== 5. ์ด๋ฉ์ผ ๋ด์ฉ ์์ฑ: generateEmail ==== ์ด ํจ์๋ โC6 + C7์ ๊ตฌ๊ธ๋ ์ค โ C8, C9โ ๋ฅผ ๋ด๋นํฉ๋๋ค. ===== 5-1. ๊ธฐ๋ณธ ์ ๊ฐ ์ฝ๊ธฐ ===== <syntaxhighlight lang="js">const sheet = SpreadsheetApp.getActiveSheet(); const ss = SpreadsheetApp.getActiveSpreadsheet(); const emailCore = sheet.getRange('C6').getValue(); </syntaxhighlight> * ํ์ฌ ํ์ฑ ์ํธ์ ์คํ๋ ๋์ํธ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ด. * C6์์ โ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉโ์ ์ฝ์. ===== 5-2. C7 ์ค๋งํธ์นฉ(๊ตฌ๊ธ ๋ ์ค ๋งํฌ) ๊ฐ์ ธ์ค๊ธฐ ===== <syntaxhighlight lang="js">const sheetName = sheet.getName(); const a1Notation = <code>'${sheetName}'!C7</code>; obj = Sheets.Spreadsheets.get(ss.getId(), { ranges: [a1Notation], fields: "sheets(data(rowData(values(userEnteredValue,chipRuns))))" }); </syntaxhighlight> * '''๊ณ ๊ธ ์๋น์ค์ธ โSheets APIโ'''๋ฅผ ์ฌ์ฉํด์, C7์ ์์ธ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค. * ์ผ๋ฐ getValue()๋ก๋ ์ค๋งํธ์นฉ ์์ URL์ ๋ฐ๋ก ์ ์ ์๊ธฐ ๋๋ฌธ์, chipRuns๋ผ๋ ๋ด๋ถ ๊ตฌ์กฐ๋ฅผ ์ฝ์ด์ค๋ ๋ฐฉ์. <syntaxhighlight lang="js">const rowData = obj.sheets[0].data[0].rowData[0]; const chipRuns = rowData.values[0].chipRuns; ... docUrl = run.chip.richLinkProperties.uri; </syntaxhighlight> * chipRuns ๋ฐฐ์ด ์์์ richLinkProperties.uri์ ์ค์ ๊ตฌ๊ธ ๋ ์ค URL์ด ๋ค์ด์์ด์ ๊ทธ๊ฑธ docUrl๋ก ๋ฝ์๋ ๋๋ค. ===== 5-3. ์ ๋ ฅ๊ฐ ์ฒดํฌ ===== <syntaxhighlight lang="js">if (!emailCore) { alert('C6์ ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉ์...'); return; } if (!docUrl) { alert('C7์ ๊ตฌ๊ธ ๋ ์ค ์ค๋งํธ์นฉ์ด ์์ต๋๋ค!'); return; } </syntaxhighlight> * ํต์ฌ ๋ด์ฉ(C6)์ด๋ ํ๋กฌํํธ ๋ ์ค(C7)๊ฐ ๋น์ด ์์ผ๋ฉด ๋ฐ๋ก ์ฌ์ฉ์์๊ฒ ์๋ฆฌ๊ณ ํจ์ ์ข ๋ฃ. ===== 5-4. ๊ตฌ๊ธ ๋ ์ค์์ ํ๋กฌํํธ ๋ด์ฉ ์ฝ๊ธฐ ===== <syntaxhighlight lang="js">const userPrompt = getDocContent(docUrl); </syntaxhighlight> * ์๋ ํฌํผ ํจ์ getDocContent๋ฅผ ํธ์ถํด์ ๊ตฌ๊ธ ๋ ์ค ๋ฌธ์ ์ ์ฒด ํ ์คํธ๋ฅผ ๊ฐ์ ธ์ด. * ๊ถํ ๋ฌธ์ ๋ URL ๋ฌธ์ ๋ก ์คํจํ๋ฉด null ๋ฐํ โ ๊ฒฝ๊ณ ํ ์ข ๋ฃ. ===== 5-5. Gemini์ ๋ณด๋ผ ์ต์ข ํ๋กฌํํธ ๊ตฌ์ฑ ===== <syntaxhighlight lang="js">const finalPrompt = <code>${userPrompt} ์ด๋ฉ์ผ ํต์ฌ ๋ด์ฉ: ${emailCore} ๋ค์ ํ์์ผ๋ก ์ ํํ๊ฒ ์๋ตํด์ฃผ์ธ์: ์ ๋ชฉ: [์ฌ๊ธฐ์ ์ด๋ฉ์ผ ์ ๋ชฉ] --- [์ฌ๊ธฐ์ ์ด๋ฉ์ผ ๋ณธ๋ฌธ]</code>; </syntaxhighlight> * ๋ ์ค์ ์ ์ด๋ โ์์ฑ ๊ฐ์ด๋ ํ๋กฌํํธโ + C6์ ํต์ฌ ๋ด์ฉ + โ๋ฐ๋์ ์ด๋ฐ ํ์์ผ๋ก ๋ตํด๋ผโ๋ผ๋ ํฌ๋งท ์ง์นจ์ ํ๋๋ก ํฉ์น ๋ฌธ์์ด. ===== 5-6. Gemini API ํธ์ถ ===== <syntaxhighlight lang="js">const payload = { contents: [{ parts: [{ text: finalPrompt }] }] }; const url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'; const options = { method: 'POST', contentType: 'application/json', headers: { 'x-goog-api-key': apiKey }, payload: JSON.stringify(payload) }; const response = UrlFetchApp.fetch(url, options); const data = JSON.parse(response.getContentText()); const content = data['candidates'][0]['content']['parts'][0]['text']; </syntaxhighlight> * Gemini 2.5 Flash ๋ชจ๋ธ์ POST ์์ฒญ. * ์๋ต์ ์ฒซ ๋ฒ์งธ ํ๋ณด ํ ์คํธ๋ฅผ ๊บผ๋. ===== 5-7. โ์ ๋ชฉ / ๋ณธ๋ฌธโ ํ์ฑ ํ C8, C9์ ์ฐ๊ธฐ ===== <syntaxhighlight lang="js">const lines = content.split('\n'); let subject = ''; let body = ''; let foundSeparator = false; for (...) { if (line.startsWith('์ ๋ชฉ:')) { subject = ... } else if (line === '---') { foundSeparator = true; } else if (foundSeparator) { body += ... } } sheet.getRange('C8').setValue(subject.trim()); sheet.getRange('C9').setValue(body.trim()); </syntaxhighlight> * ์๋ต ํ ์คํธ๋ฅผ ์ค ๋จ์๋ก ์ชผ๊ฐ์ - ์ ๋ชฉ:์ผ๋ก ์์ํ๋ ์ค โ ์ ๋ชฉ - --- ์ดํ์ ๋ด์ฉ โ ๋ณธ๋ฌธ * ์ต์ข ์ ์ผ๋ก C8, C9์ ๊ธฐ๋ก. * ์๋ฃ ํ ์๋ฆผ์ฐฝ: โ์ด๋ฉ์ผ ๋ด์ฉ์ด ์์ฑ๋์์ต๋๋ค!โ ==== 6. ์ด๋ฉ์ผ ๋์์ธ + ํ ์คํธ ๋ฐ์ก: generateEmailDesign ==== ์ด ํจ์๋ โํ ์คํธ(C9) + ๋์์ธ ํ๋กฌํํธ(C14) โ HTML(C15) + ํ ์คํธ ๋ฉ์ผ ๋ฐ์ก(C13)โ ๋ด๋น. ===== 6-1. ๊ธฐ๋ณธ ๊ฐ ๋ฐ C14 ์ค๋งํธ์นฉ ์ฒ๋ฆฌ ===== * C9์์ ์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ์ฝ๊ธฐ * C14์์, ์์ ๋์ผํ ๋ฐฉ์์ผ๋ก chipRuns๋ฅผ ํตํด ๊ตฌ๊ธ ๋ ์ค URL ์ถ์ถ * getDocContent(designPromptDocUrl)๋ก ๋์์ธ ์ง์นจ ํ ์คํธ ๊ฐ์ ธ์ค๊ธฐ * ๊ฐ ๊ฐ์ด ๋น์ด ์์ผ๋ฉด ์ ์ ํ ์๋ฆผ ํ ์ข ๋ฃ ===== 6-2. Gemini ํ๋กฌํํธ ๊ตฌ์ฑ ===== <syntaxhighlight lang="js">const finalPrompt = <code> ๋น์ ์ ์ ๋ฌธ ์ด๋ฉ์ผ HTML ๋์์ด๋์ ๋๋ค. ๋ค์์ ๋์์ด๋์๊ฒ ์ ํ๋ ๋์์ธ ์ง์นจ(ํ๋กฌํํธ)์ ๋๋ค: ---[๋์์ธ ์ง์นจ ์์]--- ${designPrompt} ---[๋์์ธ ์ง์นจ ๋]--- ๋ค์์ ์ด๋ฉ์ผ์ ๋ค์ด๊ฐ ์ค์ ํ ์คํธ ๋ด์ฉ์ ๋๋ค: ---[์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ์์]--- ${emailText} ---[์ด๋ฉ์ผ ํ ์คํธ ๋ด์ฉ ๋]--- [์์ฒญ ์ฌํญ] ... (์ธ๋ผ์ธ ์คํ์ผ, ์์ HTML๋ง, UTF-8 ๋ฑ) </code>; </syntaxhighlight> * ์ญํ ์ง์(โ์ ๋ฌธ ์ด๋ฉ์ผ HTML ๋์์ด๋โ)์ ์๊ตฌ์ฌํญ(์ธ๋ผ์ธ ์คํ์ผ, <html>...</html>๋ง, UTF-8 ๋ฑ)์ ๋ช ํํ ์ ์ด ์ค. ===== 6-3. Gemini ํธ์ถ ๋ฐ HTML ์ ๋ฆฌ ===== Gemini ํธ์ถ ๋ถ๋ถ์ generateEmail๊ณผ ๊ฑฐ์ ๋์ผ. <syntaxhighlight lang="js">htmlResult = content.trim(); if (htmlResult.startsWith('</syntaxhighlight>html')) { ... } if (htmlResult.endsWith('``<code>')) { ... } sheet.getRange('C15').setValue(htmlResult); <syntaxhighlight> * ๋ชจ๋ธ์ด ์ข ์ข html ์ฝ๋๋ธ๋ก ํํ๋ก ๋ตํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด์, ๋ฐฑํฑ์ ์ ๊ฑฐํ๊ณ ์์ HTML๋ง ๋จ๊ธฐ๊ณ C15์ ์ ์ฅ. ===== 6-4. ํ ์คํธ ๋ฉ์ผ ๋ฐ์ก ===== </syntaxhighlight>js const testEmail = sheet.getRange('C13').getValue().trim(); const subject = sheet.getRange('C8').getValue(); if (testEmail) { const emailOptions = { htmlBody: htmlResult, name: 'AI CRM ์์คํ (ํ ์คํธ)', }; GmailApp.sendEmail(testEmail, </code>[ํ ์คํธ] ${subject}<code>, "", emailOptions); ui.alert(</code>... ํ ์คํธ ๋ฉ์ผ์ด ${testEmail}๋ก ๋ฐ์ก๋์์ต๋๋ค!<code>); } else { ui.alert('์ด๋ฉ์ผ ๋์์ธ์ด ์์ฑ๋์ด C15์ ์ ๋ ฅ๋์์ต๋๋ค! (C13์ ํ ์คํธ ์ฃผ์๊ฐ ์์ด ๋ฐ์ก๋์ง ์์์ต๋๋ค)'); } <syntaxhighlight> * C13์ ํ ์คํธ ์ด๋ฉ์ผ์ด ์์ผ๋ฉด: - GmailApp.sendEmail๋ก HTML ๋ฉ์ผ ๋ฐ์ก - ์ ๋ชฉ ์์ [ํ ์คํธ] ๋ถ์ * ์์ผ๋ฉด: โ๋์์ธ์ ์ ์ฅํ์ง๋ง ๋ฐ์ก์ ์ ํ๋คโ๋ ์๋ฆผ๋ง. ==== 7. ๋จ์ฒด ๋ฉ์ผ ๋ฐ์ก: sendBulkEmail ==== ์ด ํจ์๋ ์ฃผ์๋ก ์คํ๋ ๋์ํธ + ์ค์ ์ ๋ณด โ ๊ฐ์ธํ๋ ๋๋ ๋ฐ์ก ๋ด๋น. ===== 7-1. ๋ฐ์ก ๊ธฐ๋ณธ ์ ๋ณด ๋ก๋ ===== </syntaxhighlight>js const subject = sheet.getRange('C8').getValue(); const htmlBodyTemplate = sheet.getRange('C15').getValue(); const senderEmail = sheet.getRange('C19').getValue().trim(); const senderName = sheet.getRange('C20').getValue().trim(); <syntaxhighlight> * ์ ๋ชฉ, HTML ํ ํ๋ฆฟ, ๋ฐ์ ์ ์ด๋ฉ์ผ/์ด๋ฆ ๊ฐ์ ธ์ค๊ธฐ. ===== 7-2. ์ฃผ์๋ก ์ํธ ์ ๋ณด ์ฝ๊ธฐ ===== </syntaxhighlight>js const addressBookSheetName = sheet.getRange('C22').getValue().trim(); ... // C21 ์ค๋งํธ์นฉ์์ URL ์ถ์ถ (C7/C14์ ๋์ผ ๋ฐฉ์) addressBookSheetUrl = run.chip.richLinkProperties.uri; <syntaxhighlight> * C21: ์ฃผ์๋ก ์คํ๋ ๋์ํธ ๋งํฌ(์ค๋งํธ์นฉ) * C22: ๊ทธ ์คํ๋ ๋์ํธ ์์์ ์ฌ์ฉํ ์ํธ ์ด๋ฆ ===== 7-3. ์ด๋ฉ์ผ/์ด๋ฆ ์ด ์ ๋ณด ์ฝ๊ธฐ ===== </syntaxhighlight>js const emailColumnLetter = sheet.getRange('C23').getValue().trim().toUpperCase(); const nameColumnLetter = sheet.getRange('C24').getValue().trim().toUpperCase(); <syntaxhighlight> * ์: ์ด๋ฉ์ผ ์ด์ด B, ์ด๋ฆ ์ด์ด C ๋ฑ. ===== 7-4. ํ์๊ฐ ์ฒดํฌ ===== </syntaxhighlight>js if (!subject || !htmlBodyTemplate) ... if (!addressBookSheetUrl || !addressBookSheetName || !emailColumnLetter || !nameColumnLetter) ... if (!senderName) ... <syntaxhighlight> * ๊ธฐ๋ณธ ์ค์ ์ด ๋น ์ง ๊ฒฝ์ฐ ์ฆ์ ์๋ฆผ ํ ์ค๋จ. ===== 7-5. ์ฃผ์๋ก ์คํ๋ ๋์ํธ/์ํธ ์ด๊ธฐ ===== </syntaxhighlight>js const addressBookSheetId = getDocIdFromUrl(addressBookSheetUrl); const addressBookSpreadsheet = SpreadsheetApp.openById(addressBookSheetId); const addressBookSheet = addressBookSpreadsheet.getSheetByName(addressBookSheetName); const data = addressBookSheet.getDataRange().getValues(); <syntaxhighlight> * C21์ URL์์ ๋ฌธ์ ID๋ฅผ ์ถ์ถ(getDocIdFromUrl)ํ๊ณ * openById๋ก ์ํธ ํ์ผ์ ์ด์ด, * C22์ ์ ํ ์ํธ ์ด๋ฆ์ผ๋ก ํด๋น ์ํธ๋ฅผ ์ฐพ๊ณ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ 2์ฐจ์ ๋ฐฐ์ด๋ก ๊ฐ์ ธ์ด. ===== 7-6. ์ด ๋ฌธ์ โ ์ธ๋ฑ์ค๋ก ๋ณํ ===== </syntaxhighlight>js const emailColIndex = columnLetterToIndex(emailColumnLetter); const nameColIndex = columnLetterToIndex(nameColumnLetter); <syntaxhighlight> * "A" โ 0, "B" โ 1, "AA" โ 26 ์ด๋ฐ ์์ผ๋ก ๋ฐ๊พธ๋ ํจ์. * ๋์ค์ data[row][index]๋ก ์ ๊ทผํ๊ธฐ ์ํด ํ์. ===== 7-7. ๋ฃจํ ๋๋ฉด์ ๊ฐ์ธํ + ๋ฐ์ก ===== </syntaxhighlight>js let sentCount = 0; const placeholder = /{{์ด๋ฆ}}/g; const options = { name: senderName, htmlBody: null, }; if (senderEmail) { options.from = senderEmail; } for (let i = 1; i < data.length; i++) { const recipientEmail = row[emailColIndex]; const recipientName = row[nameColIndex]; if (!recipientEmail || recipientEmail.indexOf('@') === -1) { continue; } const nameForEmail = recipientName || "๊ณ ๊ฐ"; const personalizedBody = htmlBodyTemplate.replace(placeholder, nameForEmail); options.htmlBody = personalizedBody; GmailApp.sendEmail(recipientEmail, subject, "", options); sentCount++; Utilities.sleep(1000); } ui.alert(</code>์ด ${sentCount}๊ฑด์ ์ด๋ฉ์ผ์ด ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์ก๋์์ต๋๋ค.<code>); <syntaxhighlight> * 1ํ์ ํค๋๋ผ๊ณ ๋ณด๊ณ 2ํ(i=1)๋ถํฐ ๋ฐ๋ณต. * ์ด๋ฉ์ผ์ด ๋น์ด ์๊ฑฐ๋ @๊ฐ ์์ผ๋ฉด ๊ฑด๋๋. * ์ด๋ฆ์ด ์์ผ๋ฉด โ๊ณ ๊ฐโ์ผ๋ก ๋์ฒด. * C15 ํ ํ๋ฆฟ์์ {{์ด๋ฆ}}์ด๋ผ๋ ๋ฌธ์์ด์ ํด๋น ์ฌ๋ ์ด๋ฆ์ผ๋ก ๋ชจ๋ ์นํ. * GmailApp.sendEmail๋ก HTML ๋ฐ์ก. * ๊ตฌ๊ธ ๋ฐ์ก ์ ํ(์ฟผํฐ)์ ์กฐ๊ธ ์ฌ์ ๋ฅผ ์ฃผ๊ธฐ ์ํด 1์ด์ฉ ๋๋ ์ด. * ํ ์ฌ๋์ด๋ผ๋ ์๋ฌ ๋๋ฉด ๋ฐ๋ก ์ค๋จ ๋ฐ ์๋ฆผ. ==== 8. ํฌํผ ํจ์๋ค ==== ===== 8-1. getDocIdFromUrl(url) ===== </syntaxhighlight>js const match = url.match(/[-\w]{25,}/); return match ? match[0] : null; <syntaxhighlight> * ๊ตฌ๊ธ ๋ฌธ์/์ํธ URL ์์์ 25์ ์ด์์ง๋ฆฌ ID ํจํด์ ์ ๊ท์์ผ๋ก ์ฐพ์์ ๋ฐํ. * Docs, Sheets ๋ชจ๋ ์ด๋ฐ ํ์์ด๋ผ ๊ณต์ฉ์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ. ===== 8-2. getDocContent(docUrl) ===== </syntaxhighlight>js const docId = getDocIdFromUrl(docUrl); const doc = DocumentApp.openById(docId); const body = doc.getBody(); const text = body.getText(); return text; <syntaxhighlight> * ๊ตฌ๊ธ ๋ ์ค ๋ฌธ์๋ฅผ ์ด์ด์ ๋ณธ๋ฌธ ์ ์ฒด ํ ์คํธ๋ฅผ ๋ฐํ. * ๊ถํ ๋ฌธ์ ๋ฑ์ผ๋ก ์คํจํ๋ฉด null ๋ฐํ. ===== 8-3. columnLetterToIndex(letter) ===== </syntaxhighlight>js let column = 0; for (...) { column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1); } return column - 1; </code>`` * ์์ /์ํธ์ โ์ด ๋ฌธ์โ๋ฅผ ์ซ์ ์ธ๋ฑ์ค๋ก ๋ฐ๊พธ๋ ํจ์. - "A" โ 0, "B" โ 1, "Z" โ 25, "AA" โ 26 โฆ ==== 9. ์ด๋ณด์์๊ฒ ์ค๋ช ํ ๋ ํฌ์ธํธ ์ ๋ฆฌ ==== ๋ฐํ/์ค๋ช ํ ๋๋ ์ด๋ ๊ฒ ์์ฝํด ์ฃผ๋ฉด ์ดํด๊ฐ ์ฝ์ต๋๋ค. # โ์ด ์์คํ ์ 3๋จ๊ณ ์๋ํ ํ์ดํ๋ผ์ธ์ด๋คโ 1. ๊ธ์ ์ ์ฐ๋ AI์๊ฒ ๋ด์ฉ(์ ๋ชฉ+๋ณธ๋ฌธ)์ ๋ฐ์์ค๊ณ 2. ๋์์ด๋ ์ญํ ์ AI์๊ฒ HTML ๋์์ธ์ ๋ฐ์์ค๊ณ 3. ์ฃผ์๋ก์ ์ฝ์ด์ ์ฌ๋ ์ด๋ฆ์ ๋ผ์ ๋ฃ์ด ๋จ์ฒด ๋ฐ์กํ๋ค. # โ๊ฐ ์ ์ ์ญํ ๋ง ๊ธฐ์ตํ๋ฉด ๋๋คโ - C6, C7 โ ๋ด์ฉ ์์ฑ์ ์ฐ์ด๋ ์ฌ๋ฃ - C8, C9 โ ์ต์ข ์ ๋ชฉ/๋ณธ๋ฌธ - C13, C14, C15 โ ํ ์คํธ์ฉ ๋์์ธ ์์ฑ ๋ฐ ๋ฐ์ก - C19~C24 โ ๋จ์ฒด ๋ฐ์ก ์ค์ (๋ฐ์ ์, ์ฃผ์๋ก ์์น, ์ด ์ ๋ณด) # โ์ฝ๋๋ ๊ทธ ๊ณผ์ ์ ์๋์ผ๋ก ์ํํ๋ ๋ฒํผ์ ๋โ - ๋ฉ๋ด ํจ์: ๋ฒํผ์ ๋ง๋ ๋ค. - generateEmail: ๊ธ์ฐ๊ธฐ AI ํธ์ถ ๋ด๋น - generateEmailDesign: ๋์์ธ AI + ํ ์คํธ ๋ฐ์ก ๋ด๋น - sendBulkEmail: ์ฃผ์๋ก + ๊ฐ์ธํ + ๋จ์ฒด ๋ฐ์ก ๋ด๋น - ํฌํผ ํจ์: URL์์ ID ๋ฝ๊ธฐ, ๋ ์ค ์ฝ๊ธฐ, ์ด ๋ฌธ์ ๋ณํ ๊ฐ์ ๋ฐ๋ณต ์์ ๋ด๋น ์ด ์ ๋ ๊ตฌ์กฐ๋ก ์ค๋ช ํ๋ฉด, ๊ฐ๋ฐ์ ์ ๋ชจ๋ฅด๋ ์ฌ๋๋ โ์ ์ฒด ๊ทธ๋ฆผโ๊ณผ โ๊ฐ ํจ์ ์ญํ โ์ ๊ฝค ๋ช ํํ๊ฒ ์ดํดํ ์ ์์ต๋๋ค. == References == <references /> [[Category:ChatGPT Shares]] [[Category:openai]]
Summary:
Please note that all contributions to freem are considered to be released under the Creative Commons Attribution-ShareAlike 4.0 (see
Freem:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:Cite web
(
edit
)
Template:์ด๋ฆ
(
edit
)
Module:Citation/CS1
(
edit
)
Module:Citation/CS1/COinS
(
edit
)
Module:Citation/CS1/Configuration
(
edit
)
Module:Citation/CS1/Date validation
(
edit
)
Module:Citation/CS1/Identifiers
(
edit
)
Module:Citation/CS1/Utilities
(
edit
)
Module:Citation/CS1/Whitelist
(
edit
)
Module:Citation/CS1/styles.css
(
edit
)