{"id":21340248,"url":"https://github.com/mnestorov/bulk-email-sender","last_synced_at":"2026-03-19T20:00:19.053Z","repository":{"id":249651384,"uuid":"832115373","full_name":"mnestorov/bulk-email-sender","owner":"mnestorov","description":"This Google Apps Script allows you to send bulk emails based on data from a Google Sheets document.","archived":false,"fork":false,"pushed_at":"2024-11-07T11:08:12.000Z","size":55,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-22T15:28:34.433Z","etag":null,"topics":["apps-script","bulk-email-sender","bulk-email-sending","email-sender","google","google-apps-script","google-sheets"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mnestorov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-22T11:40:29.000Z","updated_at":"2024-11-07T11:08:15.000Z","dependencies_parsed_at":"2024-11-07T12:19:50.535Z","dependency_job_id":"7a8b2ff7-e09c-48d0-8314-69f2ba7800c3","html_url":"https://github.com/mnestorov/bulk-email-sender","commit_stats":null,"previous_names":["mnestorov/bulk-email-sender"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnestorov%2Fbulk-email-sender","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnestorov%2Fbulk-email-sender/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnestorov%2Fbulk-email-sender/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mnestorov%2Fbulk-email-sender/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mnestorov","download_url":"https://codeload.github.com/mnestorov/bulk-email-sender/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243817404,"owners_count":20352554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apps-script","bulk-email-sender","bulk-email-sending","email-sender","google","google-apps-script","google-sheets"],"created_at":"2024-11-22T00:49:50.561Z","updated_at":"2026-01-03T10:05:33.020Z","avatar_url":"https://github.com/mnestorov.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bulk Email Sender using Google Apps Script\n\n[![Licence](https://img.shields.io/github/license/Ileriayo/markdown-badges?style=for-the-badge)](./LICENSE)\n\n## Support The Project\n\nYour support is greatly appreciated and will help ensure all of the projects continued development and improvement. Thank you for being a part of the community!\nYou can send me money on Revolut by following this link: https://revolut.me/mnestorovv\n\n## Overview\n\nThis Google Apps Script allows you to send bulk emails based on data from a Google Sheets document. The script reads the email template and recipient details from the Google Sheets, sends emails accordingly, and logs the email status in another sheet.\n\n## Features\n\n- **Bulk Email Sending**: Sends emails to multiple recipients with customized content.\n- **Template-Based Emails**: Email content can be customized using templates.\n- **Dynamic Content Replacement**: Order ID, COD Price, Currency, and Name placeholders in the email body and subject are replaced with actual values.\n   - **Dynamic Tags**: `\u003c\u003cOrder ID\u003e\u003e` | `\u003c\u003cCOD Price\u003e\u003e` | `\u003c\u003cName\u003e\u003e` | `\u003c\u003cCurrency\u003e\u003e` | `\u003c\u003cPAYMENT_DETAILS\u003e\u003e`\n- **Logging**: Logs the status of each email sent, including the timestamp and the sender's email address.\n- **Fallback Email Handling**: Uses a default sender email if none is provided in the template.\n\n## Setup\n\n1. **Create a Google Sheet**:\n\n   - Create a Google Sheet with three sheets: `Orders`, `Email Template` and `Email Log`.\n   \n2. **Orders Sheet**:\n\n   - The `Orders` sheet should have the following columns starting from the first row:\n\n     ```\n     | Order ID | COD Price | Currency | Name | Email | Language |\n     ```\n   - Fill in the order details accordingly.\n\n3. **Email Template Sheet**:\n\n   - The `Email Template` sheet should have the following structure:\n\n     ```\n     | Content       | en            | es            | fr            | ... |\n     | --------------|---------------|---------------|---------------|-----|\n     | Email Title   | Title in EN   | Title in ES   | Title in FR   | ... |\n     | Email Subject | Subject in EN | Subject in ES | Subject in FR | ... |\n     | Email Header  | Header in EN  | Header in ES  | Header in FR  | ... |\n     | Email Body    | Body in EN    | Body in ES    | Body in FR    | ... |\n     | Sender Email  | sender@example.com                                  |\n     | Logo URL      | https://example.com/logo.png                        |\n     -----------------------------------------------------------------------\n     ```\n\n   - Example `Email Subject` template 1:\n\n     ```\n     SITE_NAME order number #\u003c\u003cOrder ID\u003e\u003e - Order received\n     ```\n    \n   - Example `Email Body` template 1:\n\n     ```\n     Hello \u003c\u003cName\u003e\u003e, \u003cbr\u003e\u003cbr\u003e\n\n     Your Order ID is \u003c\u003cOrder ID\u003e\u003e with a price of \u003c\u003cCOD Price\u003e\u003e \u003c\u003cCurrency\u003e\u003e. \u003cbr\u003e\u003cbr\u003e\n\n     Thank you for your purchase! \u003cbr\u003e\u003cbr\u003e\n\n     Regards,\u003cbr\u003e\n     SITE_NAME!\n     ```\n\n    - Example `Email Subject` template 2:\n\n      ```\n      SITE_NAME order number #\u003c\u003cOrder ID\u003e\u003e - Missing payment\n      ```\n    \n   - Example `Email Body` template 2:\n\n      ```\n      Hello \u003c\u003cName\u003e\u003e,\u003cbr\u003e\u003cbr\u003e \n\n      We are contacting you regarding your SITE_NAME order \u003cb\u003e#\u003c\u003cOrder ID\u003e\u003e\u003c/b\u003e and amount to pay: \u003cb\u003e\u003c\u003cCOD Price\u003e\u003e \u003c\u003cCurrency\u003e\u003e\u003c/b\u003e.\u003cbr\u003e\u003cbr\u003e \n      \n      We have not yet received payment for your order.\u003cbr\u003e\u003cbr\u003e \n      \n      You can pay for your order via: \u003cbr\u003e\u003cbr\u003e \n      \n      \u003c\u003cPAYMENT_DETAILS\u003e\u003e\n      \n      As a reason for payment, enter your order ID.\u003cbr\u003e\u003cbr\u003e \n      \n      Thank you in advance and we apologize for the inconvenience!\u003cbr\u003e\u003cbr\u003e \n      \n      Sincerely,\u003cbr\u003e\n      SITE_NAME!\n      ```    \n\n4. **Payment Details Sheet**\n\n   - The Payment Details sheet should have the following structure:\n   \n     ```\n     | en           |                ...                |          ...          |\n     ----------------------------------------------------------------------------\n     |              | PayPal                            | your_email@address    |\n     |              |                                   |                       |\n     |              | You can make payment also via:    |                       |\n     |              | Beneficient                       | YOUR COMPANY          |\n     |              | Bank                              | YOUR BANK             |\n     |              | IBAN                              | YOUR IBAN             |\n     |              | SWIFT                             | BANK SWIFT            |\n     |              | Bank Address                      | BANK ADDRESS          |\n     ----------------------------------------------------------------------------\n     ```\n\n6. **Email Log Sheet**\n\n   - The Email Log sheet should have the following structure:\n\n     ```\n     | Timestamp | Order ID | COD Price | Currency | Name | Email | Language | Status | Send By | SM |\n     ```\n   - `SM` is the count of the sended emails for each user.\n     \n7. **Google Apps Script**:\n\n   - Open the script editor in your Google Sheet (`Extensions` \u003e `Apps Script`).\n   - Copy and paste the provided script into the script editor.\n   - Save the script.\n\n8. **Authorization**:\n\n   - Run the script for the first time and authorize the required permissions.\n\n## Usage\n\n1. **Run the Script**:\n\n   - Open the Google Sheet and run the script by clicking the button or running the function `sendBulkEmails` from the script editor.\n   - A confirmation dialog will appear. Click \"Yes\" to send the emails.\n\n3. **Check Logs**:\n\n   - After running the script, check the `Email Log` sheet to see the log entries, including the timestamp, order details, status, and sender email.\n\n## Code.gs\n\n```javascript\nfunction sendBulkEmails() {\n  var ui = SpreadsheetApp.getUi();\n  var response = ui.alert('Send Emails', 'Do you want to send emails to all users?', ui.ButtonSet.YES_NO);\n\n  if (response == ui.Button.NO) {\n    Logger.log('User canceled the email sending process.');\n    logCancelStatus();\n    return;\n  }\n\n  try {\n    var userEmail = getUserEmail(); // Get the email of the person running the script\n\n    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Orders');\n    if (!sheet) {\n      Logger.log('Orders sheet not found');\n      return;\n    }\n    var dataRange = sheet.getRange(2, 1, sheet.getLastRow() - 1, 6); // Read 6 columns: Order ID, COD Price, Currency, Name, Email, Language\n    var data = dataRange.getValues();\n  \n    var templateSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Email Template');\n    if (!templateSheet) {\n      Logger.log('Email Template sheet not found');\n      return;\n    }\n    var templateHeaders = templateSheet.getRange(1, 2, 1, templateSheet.getLastColumn() - 1).getValues()[0]; // Get language headers\n    var templates = {};\n\n    // Common values for all languages\n    var senderEmail = templateSheet.getRange('B6').getValue(); // Read sender email\n    var logoUrl = templateSheet.getRange('B7').getValue(); // Read logo URL\n\n    // Use user email if sender email is empty\n    if (!senderEmail) {\n      senderEmail = userEmail;\n    }\n\n    // Load templates for each language using ISO codes\n    for (var i = 0; i \u003c templateHeaders.length; i++) {\n      var isoCode = templateHeaders[i];\n      templates[isoCode] = {\n        emailTitle: templateSheet.getRange(2, i + 2).getValue(),\n        emailSubject: templateSheet.getRange(3, i + 2).getValue(),\n        emailHeader: templateSheet.getRange(4, i + 2).getValue(),\n        emailBodyTemplate: templateSheet.getRange(5, i + 2).getValue()\n      };\n    }\n\n    var logSheet = getLogSheet();\n    var paymentDetails = getPaymentDetails(); // Get payment details from the Payment Details sheet\n  \n    for (var i = 0; i \u003c data.length; i++) {\n      var orderId = data[i][0];\n      var codPrice = data[i][1];\n      var currency = data[i][2];\n      var name = data[i][3];\n      var email = data[i][4];\n      var language = data[i][5] || 'en'; // Default to English if language is empty\n      \n      var template = templates[language] || templates['en']; // Default to English if language not found\n\n      var emailSubject = template.emailSubject.replace('\u003c\u003cOrder ID\u003e\u003e', orderId);\n      var emailBody = template.emailBodyTemplate.replace('\u003c\u003cOrder ID\u003e\u003e', orderId)\n                                                 .replace('\u003c\u003cCOD Price\u003e\u003e', codPrice)\n                                                 .replace('\u003c\u003cName\u003e\u003e', name)\n                                                 .replace('\u003c\u003cCurrency\u003e\u003e', currency);\n\n      var paymentInfo = paymentDetails[language] || {};\n      var paymentDetailsString = formatPaymentDetails(paymentInfo);\n\n      emailBody = emailBody.replace('\u003c\u003cPAYMENT_DETAILS\u003e\u003e', paymentDetailsString);\n\n      emailBody = `\n        \u003cdiv style=\"max-width: 600px; margin: 0 auto; border: 1px solid #fff; padding: 0; font-family: Arial, sans-serif;\"\u003e\n          \u003cdiv style=\"text-align: center;\"\u003e\n            \u003cimg src=\"${logoUrl}\" alt=\"Logo\" style=\"max-width: 140px; height: auto;\"/\u003e\n          \u003c/div\u003e\n          \u003cdiv style=\"background-color: #e91e63; color: white; text-align: center; padding: 10px; font-size: 24px; font-weight: bold; text-transform: capitalize;\"\u003e\n            ${template.emailHeader}\n          \u003c/div\u003e\n          \u003cdiv style=\"padding: 20px; font-size: 16px; color: #333;\"\u003e\n            ${emailBody}\n          \u003c/div\u003e\n        \u003c/div\u003e\n      `;\n  \n      try {\n        GmailApp.sendEmail(\n          email,\n          emailSubject, // Use the dynamic subject with Order ID\n          '', // Plain text body is empty because we use HTML body\n          {\n            from: senderEmail, // Use the sender email if provided, otherwise use the user's email\n            name: template.emailTitle, // Set the name to appear in the sender field,\n            htmlBody: emailBody // HTML body content\n          }\n        );\n        Logger.log('Email sent to: ' + email);\n        logEmailResult(logSheet, orderId, codPrice, currency, name, email, language, 'Sent', senderEmail);\n      } catch (e) {\n        Logger.log('Failed to send email to: ' + email + '. Error: ' + e.message);\n        logEmailResult(logSheet, orderId, codPrice, currency, name, email, language, 'Failed', senderEmail);\n      }\n  \n      Utilities.sleep(1000); // 1 second delay between emails\n    }\n  } catch (e) {\n    Logger.log('Error in sendBulkEmails function: ' + e.message);\n  }\n}\n\nfunction getPaymentDetails() {\n  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Payment Details');\n  if (!sheet) {\n    Logger.log('Payment Details sheet not found');\n    return {};\n  }\n  var dataRange = sheet.getDataRange();\n  var data = dataRange.getValues();\n\n  var paymentDetails = {};\n  var currentCountry = null;\n\n  for (var i = 0; i \u003c data.length; i++) {\n    var row = data[i];\n    if (row[0] \u0026\u0026 !row[1]) { // New country section\n      currentCountry = row[0];\n      paymentDetails[currentCountry] = [];\n    } else if (currentCountry \u0026\u0026 row[1] === \"\" \u0026\u0026 row[2] === \"\") { // Handle blank line\n      paymentDetails[currentCountry].push({label: \"\", value: \"\"});\n    } else if (currentCountry \u0026\u0026 row[1] \u0026\u0026 row[2]) { // Add details to current country\n      paymentDetails[currentCountry].push({label: row[1], value: row[2]});\n    }\n  }\n\n  return paymentDetails;\n}\n\nfunction formatPaymentDetails(paymentInfo) {\n  var paymentDetailsString = \"\";\n  for (var i = 0; i \u003c paymentInfo.length; i++) {\n    if (paymentInfo[i].label === \"\" \u0026\u0026 paymentInfo[i].value === \"\") {\n      paymentDetailsString += `\u003cbr\u003e`; // Add blank line\n    } else {\n      paymentDetailsString += `\u003cb\u003e${paymentInfo[i].label}:\u003c/b\u003e ${paymentInfo[i].value}\u003cbr\u003e`;\n    }\n  }\n  return paymentDetailsString;\n}\n\nfunction getUserEmail() {\n  try {\n    return Session.getActiveUser().getEmail();\n  } catch (e) {\n    Logger.log('Error getting user email: ' + e.message);\n    return 'Unknown';\n  }\n}\n\nfunction getLogSheet() {\n  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();\n  var logSheet = spreadsheet.getSheetByName('Email Log');\n  if (!logSheet) {\n    logSheet = spreadsheet.insertSheet('Email Log');\n  }\n  if (logSheet.getLastRow() === 0) {\n    logSheet.appendRow(['Timestamp', 'Order ID', 'COD Price', 'Currency', 'Name', 'Email', 'Language', 'Status', 'Sent From', 'SE']);\n  }\n  return logSheet;\n}\n\nfunction logEmailResult(logSheet, orderId, codPrice, currency, name, email, language, status, senderEmail) {\n  var timestamp = new Date();\n  var formattedTimestamp = Utilities.formatDate(timestamp, Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss');\n  var sentEmailCount = getEmailCount(logSheet, email) + 1; // Increment the count by 1\n  logSheet.appendRow([formattedTimestamp, orderId, codPrice, currency, name, email, language, status, senderEmail, sentEmailCount]);\n}\n\nfunction getEmailCount(logSheet, email) {\n  var data = logSheet.getDataRange().getValues();\n  var count = 0;\n  for (var i = 1; i \u003c data.length; i++) {\n    if (data[i][5] === email) {\n      count++;\n    }\n  }\n  return count;\n}\n\nfunction logCancelStatus() {\n  var logSheet = getLogSheet();\n  var timestamp = new Date();\n  var formattedTimestamp = Utilities.formatDate(timestamp, Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm:ss');\n  var senderEmail = getUserEmail(); // Get the email of the person running the script\n  logSheet.appendRow([formattedTimestamp, '', '', '', '', '', '', 'Canceled', senderEmail, '']);\n}\n```\n## appsscript.json\n\n```json\n{\n  \"timeZone\": \"Europe/Sofia\",\n  \"dependencies\": {\n    \"enabledAdvancedServices\": [\n      {\n        \"userSymbol\": \"Gmail\",\n        \"version\": \"v1\",\n        \"serviceId\": \"gmail\"\n      },\n      {\n        \"userSymbol\": \"Drive\",\n        \"version\": \"v3\",\n        \"serviceId\": \"drive\"\n      },\n      {\n        \"userSymbol\": \"Sheets\",\n        \"version\": \"v4\",\n        \"serviceId\": \"sheets\"\n      }\n    ]\n  },\n  \"oauthScopes\": [\n    \"https://mail.google.com/\",\n    \"https://www.googleapis.com/auth/spreadsheets\",\n    \"https://www.googleapis.com/auth/userinfo.email\",\n    \"https://www.googleapis.com/auth/script.external_request\"\n  ],\n  \"exceptionLogging\": \"STACKDRIVER\",\n  \"runtimeVersion\": \"V8\"\n}\n```\n\n## Contributing\n\nWe welcome contributions from the community! If you would like to contribute, please fork the repository, make changes, and submit a pull request. You can also open an issue to discuss potential changes.\n\n## Thank You\n\nIf you have any questions, feel free to reach out.\n\n## Support The Project\n\nIf you find this script helpful and would like to support its development and maintenance, please consider the following options:\n\n- **_Star the repository_**: If you're using this script from a GitHub repository, please give the project a star on GitHub. This helps others discover the project and shows your appreciation for the work done.\n\n- **_Share your feedback_**: Your feedback, suggestions, and feature requests are invaluable to the project's growth. Please open issues on the GitHub repository or contact the author directly to provide your input.\n\n- **_Contribute_**: You can contribute to the project by submitting pull requests with bug fixes, improvements, or new features. Make sure to follow the project's coding style and guidelines when making changes.\n\n- **_Spread the word_**: Share the project with your friends, colleagues, and social media networks to help others benefit from the script as well.\n\n- **_Donate_**: Show your appreciation with a small donation. Your support will help me maintain and enhance the script. Every little bit helps, and your donation will make a big difference in my ability to keep this project alive and thriving.\n\nYour support is greatly appreciated and will help ensure all of the projects continued development and improvement. Thank you for being a part of the community!\nYou can send me money on Revolut by following this link: https://revolut.me/mnestorovv\n\n---\n\n## License\n\nThis project is released under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnestorov%2Fbulk-email-sender","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmnestorov%2Fbulk-email-sender","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmnestorov%2Fbulk-email-sender/lists"}