Title: Openai/691c9bd6-70c4-8009-9802-3a9e356e3130 - freem URL Source: https://freemwiki.com/index.php?mobileaction=toggle_view_mobile&title=Openai%2F691c9bd6-70c4-8009-9802-3a9e356e3130 Markdown Content: ### User: // API 키 설정 Script Properties에서 가져오기[edit](https://freemwiki.com/index.php?title=Openai/691c9bd6-70c4-8009-9802-3a9e356e3130&action=edit§ion=2 "Edit section: 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'](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 등에서의 호환성 보장). * 반응형 디자인을 고려하여