Gmail met Discord en back-up

Welkom bij deze kleine gids, in deze gids gaan we kijken hoe we het voor elkaar krijgen om nieuwe binnenkomende e-mails (ongelezen) door te sturen als een notificatie in Discord (op je eigen server), verder gaan we ook proberen een mooie back up te maken naar Google Drive van de inkomende Gmail. Dit allemaal Automatisch met behulp van Google Apps Script.


Benodigd:
– Gmail Account
– Google Drive
– Discord Account
– Eigen Discord Server
– Google Apps Script
– Discord Web Hook

Kennisgeving:

Wat een termen weer bij benodigdheden, we lopen even er snel doorheen van wat alles nu is en dan gaan we aan de gang.

Gmail:
De E-Mail service van google, de e-mail adressen eindigen op gmail.com. Dit adres is ook benodigd voor Google Drive.

Google Drive:
De cloud opslag van google, gratis accounts hebben tot 5 Gb opslag

Discord Account:
Discord is een chat programma, vergelijkbaar met andere programma’s zoals teams/telegram/whatsapp. Hier heb je dus een account voor nodig.

Discord Server:
Je kan gemakkelijk een Discord server zelf maken voor prive grebruik, dit is een soort van prive omgeving in discord, als je wilt kan je er altijd iemand op uitnodigen want het blijft natuurlijk een  echte server.

Google Apps script:
Apps Script is een programmeer tooltje van google dat vooral werkt met google diensten zoals google drive/gmail maar ook met google sheets, hier kan je dus stukjes code inzetten wat veelal lijkt op de programmeer taal javascript.

Discord Webhook:
Als je eenmaal een server hebt, dan heb je standaard 1 kanaal. je kan zoveel kanalen en catagorieën maken als je wilt. Daar kan je weer een Webhook aan koppelen, een webhook is eigenlijk een soort van Discordbot voor privegebruik specifiek voor dat kanaal.

Het begin: Google

Daar gaan we dan, nu gaat het echt beginnen.

Stap 1, Je google account, Gmail en Google drive:

Mocht je geen google account hebben, ga naar: https://support.google.com/accounts/answer/27441?hl=nl&co=GENIE.Platform%3DDesktop en volg deze gids.

Nadat je een account hebt gemaakt heb je automatisch een gmail, de directe link is https://mail.google.com deze hebben we nodig, dus zet maar open die handel.

Ook geldt dit voor google drive: https://drive.google.com zet deze ook maar open want hier gaan we beginnen.


Als je drive open hebt kies Nieuwe map en noem deze map Mail, of iets anders herkenbaar voor jou.


Open deze folder en kijk in je adresbalk, kopieer van de adresbalk het laatste stukje achter folders/ dit is namelijk je Folder ID en die hebben we later nodig.

Nu in de ma Mail, klik rechts en kies Nieuwe Map. Hier maken we 2 mappen aan genaamd bijlagen en de andere map heet pdf.

De volgende stap: Discord

Mocht je geen Discord hebben, volg deze link: https://support.discord.com/hc/nl/articles/360033931551-Aan-de-slag-gaan-met-Discord om deze gids te volgen en een account aan te maken.

Nadat je discord hebt aangemaakt en bent ingelogd in de Discord app of de webomgeving van Discord gaan we nu een Eigen Server maken.


Druk op voeg server toe en Druk hier op zelf creëren.


Voor mij en mijn vrienden.


Geef het beestje een naam en een leuk plaatje.


Dan zie je nu dit, laat het kanaal algemeen met rust.

We gaan nu verder met een kanaal maken voor je e-mail, rechterklik onder algemeen en kies Creëer kanaal


Geef het een naam bijvoorbeeld mail, zet het op Privékanaal.Klik op volgende en rond het af.


Druk nu op Kanaal bewerken


Kies Integraties en druk op webhooks.

Geef de Hook een naam en klik op Webhook-link kopiëren. Sla deze link ergens op die hebben we zo meteen nodig.

En als laatste: Google Apps Script

Ga naar: https://script.google.com/home/ en druk op Nieuw Project



Nu zie je dit, wijzig bovenin de naam naar bijvoorbeeld mail.

Kopieëer en plak de volgende code:

const WEBHOOK_URL = "VUL HIER JE WEBHOOK IN”;

const DRIVE_FOLDER_ID = "VUL HIER JE FOLDER ID IN”;

const ATTACHMENTS_FOLDER_NAME = "bijlagen";

const PDF_FOLDER_NAME = "pdf";

// Maak of haal de PDF-subfolder op

function getPdfFolder() {

  const rootFolder = DriveApp.getFolderById(DRIVE_FOLDER_ID);

  let pdfFolder = rootFolder.getFoldersByName(PDF_FOLDER_NAME);

  return pdfFolder.hasNext() ? pdfFolder.next() : rootFolder.createFolder(PDF_FOLDER_NAME);

}

// Maak of haal de bijlagen-subfolder op

function getAttachmentsFolder() {

  const rootFolder = DriveApp.getFolderById(DRIVE_FOLDER_ID);

  let attachmentsFolder = rootFolder.getFoldersByName(ATTACHMENTS_FOLDER_NAME);

  return attachmentsFolder.hasNext() ? attachmentsFolder.next() : rootFolder.createFolder(ATTACHMENTS_FOLDER_NAME);

}

// Alleen gevaarlijke tags verwijderen (script/style), behoud de rest

function sanitizeHtml(html) {

  return html

    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')

    .replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');

}

// Vereenvoudigde CSS voor betere PDF weergave

function getPdfCss() {

  return `

    body {

      font-family: Arial, sans-serif;

      margin: 20px;

      line-height: 1.5;

      color: #333;

      text-align: left;

    }

    .header {

      background-color: #f5f5f5;

      padding: 15px;

      border-radius: 5px;

      margin-bottom: 20px;

    }

    .from { font-weight: bold; }

    .subject {

      font-size: 1.3em;

      color: #2c3e50;

      margin: 10px 0;

    }

    .body {

      margin: 15px 0;

      word-wrap: break-word;

      white-space: pre-wrap;

    }

    .body img, img {

      max-width: 100%;

      height: auto;

      display: block;

      margin: 10px 0;

    }

    a[href^="http"] {

      color: #3498db;

      text-decoration: underline;

    }

    .attachments {

      margin-top: 20px;

      border-top: 1px solid #ddd;

      padding-top: 10px;

    }

    hr {

      border: 0;

      height: 1px;

      background: #ddd;

      margin: 20px 0;

    }

  `;

}

// Vervang cid: referenties door base64 (voor inline afbeeldingen)

function replaceCidWithBase64(html, attachments, usedAttachmentIndices = new Set()) {

  let processedHtml = html;

  const cidMatches = [...processedHtml.matchAll(/src="cid:([^"]*)"/gi)];

  for (const match of cidMatches) {

    const cid = match[1];

    for (let i = 0; i < attachments.length; i++) {

      if (!usedAttachmentIndices.has(i)) {

        const att = attachments[i];

        const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(att.getName());

        if (isImage) {

          const blob = att.copyBlob();

          const base64 = Utilities.base64Encode(blob.getBytes());

          const mimeType = blob.getContentType();

          processedHtml = processedHtml.replace(

            new RegExp(`src="cid:${cid}"`, 'gi'),

            `src="data:${mimeType};base64,${base64}"`

          );

          usedAttachmentIndices.add(i);

          break;

        }

      }

    }

  }

  return { html: processedHtml, usedAttachmentIndices };

}

// Verwijder alleen <a> tags die <img> bevatten

function cleanHtmlBody(html) {

  return html.replace(/<a\b[^>]*>([\s\S]*?<img\b[^>]*>[\s\S]*?)<\/a>/gi, '$1');

}

// Send to Discord with retry logic en betere foutmeldingen

function sendToDiscord(payload) {

  const options = {

    method: "post",

    contentType: "application/json",

    payload: JSON.stringify(payload),

    muteHttpExceptions: true

  };

  let success = false;

  let retries = 3;

  let lastError = "Onbekende fout";

  while (retries > 0 && !success) {

    try {

      const response = UrlFetchApp.fetch(WEBHOOK_URL, options);

      const statusCode = response.getResponseCode();

      const responseText = response.getContentText();

      if (statusCode >= 200 && statusCode < 300) {

        success = true;

      } else if (statusCode === 429) {

        // Rate limit: wacht 5 seconden en probeer opnieuw

        Utilities.sleep(5000);

        retries--;

        lastError = `Rate limit (429) - Poging ${3 - retries}/3`;

      } else {

        // Log de DAADWERKELIJKE fout

        lastError = `Discord error ${statusCode}: ${responseText.substring(0, 200)}`;

        retries = 0; // Geen retry bij andere fouten

      }

    } catch (fetchError) {

      lastError = `Fetch fout: ${fetchError.toString()}`;

      retries = 0;

    }

  }

  if (!success) {

    Logger.log(`❌ Discord fout: ${lastError}`);

  }

  return success;

}

function forwardEmailsToDiscord() {

  const threads = GmailApp.search("is:unread");

  const attachmentsFolder = getAttachmentsFolder();

  const pdfFolder = getPdfFolder();

  threads.forEach(thread => {

    const messages = thread.getMessages();

    messages.forEach(message => {

      try {

        const subject = message.getSubject() || "Geen onderwerp";

        const from = message.getFrom() || "Onbekende afzender";

        const messageId = message.getId();

        const plainBody = message.getPlainBody() || "";

        const gmailLink = `https://mail.google.com/mail/u/0/#inbox/${messageId}`;

        // --- VERWERK BODY VOOR HTML ---

        let htmlBody = message.getBody();

        const attachments = message.getAttachments();

        const usedAttachmentIndices = new Set();

        const cidResult = replaceCidWithBase64(htmlBody, attachments, usedAttachmentIndices);

        htmlBody = cidResult.html;

        htmlBody = cleanHtmlBody(htmlBody);

        htmlBody = sanitizeHtml(htmlBody);

        // --- VERWERK BIJLAGEN ---

        let attachmentHtml = "";

        let attachmentLinks = [];

        let hasAttachments = false;

        try {

          if (attachments && attachments.length > 0) {

            hasAttachments = true;

            attachmentHtml = '<div class="attachments"><strong>Bijlagen:</strong>';

            attachments.forEach((att, index) => {

              const fileName = att.getName();

              const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(fileName);

              const fileSize = (att.getSize() / 1024).toFixed(2) + " KB";

              const savedFile = attachmentsFolder.createFile(att.copyBlob());

              const fileLink = `https://drive.google.com/file/d/${savedFile.getId()}/view?usp=sharing`;

              if (isImage && !usedAttachmentIndices.has(index)) {

                const blob = att.copyBlob();

                const base64 = Utilities.base64Encode(blob.getBytes());

                const mimeType = blob.getContentType();

                attachmentHtml += `<div style="margin: 10px 0;"><img src="data:${mimeType};base64,${base64}" style="max-width: 100%; height: auto;" alt="${fileName}"></div>`;

              } else if (!isImage) {

                attachmentHtml += `<div style="margin: 10px 0;">📎 <a href="${fileLink}" target="_blank">${fileName} (${fileSize})</a></div>`;

              }

              // Maximaal 5 bijlagen in Discord embed (voorkomt 6000 char limit)

              if (attachmentLinks.length < 5) {

                attachmentLinks.push(`[${fileName}](${fileLink}) (${fileSize})`);

              }

            });

            attachmentHtml += '</div>';

          }

        } catch (attachError) {

          Logger.log("Bijlagen fout: " + attachError.toString());

          attachmentLinks = ["[Bijlagen konden niet worden verwerkt]"];

        }

        // --- GENEREREN HTML (met vereenvoudigde CSS voor PDF) ---

        const simpleCss = getPdfCss();

        const htmlContent = `

          <!DOCTYPE html>

          <html>

            <head>

              <meta charset="UTF-8">

              <title>${subject.replace(/[\\/:*?"<>|]/g, "_")}</title>

              <style>${simpleCss}</style>

            </head>

            <body>

              <div class="header">

                <div class="from">Van: ${from}</div>

                <div class="subject">Onderwerp: ${subject}</div>

                <div>Datum: ${new Date(message.getDate()).toLocaleString("nl-NL")}</div>

              </div>

              <div class="body">

                ${htmlBody}

              </div>

              ${attachmentHtml}

              <hr>

              <p><a href="${gmailLink}">Bekijk originele e-mail in Gmail</a></p>

            </body>

          </html>

        `;

        // --- SLA HTML OP IN DRIVE ---

        let htmlDriveUrl = "";

        try {

          const htmlBlob = HtmlService.createHtmlOutput(htmlContent)

            .getAs("text/html")

            .setName(`${subject.replace(/[\\/:*?"<>|]/g, "_")}.html`);

          const rootFolder = DriveApp.getFolderById(DRIVE_FOLDER_ID);

          const htmlFile = rootFolder.createFile(htmlBlob);

          htmlFile.setName(`${subject.replace(/[\\/:*?"<>|]/g, "_")}.html`);

          htmlDriveUrl = `https://drive.google.com/file/d/${htmlFile.getId()}/view?usp=sharing`;

        } catch (htmlError) {

          Logger.log("HTML generatie fout: " + htmlError.toString());

        }

        // --- GENEREREN PDF MET VEREENVOUDIGDE CSS ---

        let pdfDriveUrl = "";

        try {

          const pdfBlob = HtmlService.createHtmlOutput(htmlContent)

            .getAs("application/pdf")

            .setName(`${subject.replace(/[\\/:*?"<>|]/g, "_")}.pdf`);

          const pdfFile = pdfFolder.createFile(pdfBlob);

          pdfFile.setName(`${subject.replace(/[\\/:*?"<>|]/g, "_")}.pdf`);

          pdfDriveUrl = `https://drive.google.com/file/d/${pdfFile.getId()}/view?usp=sharing`;

        } catch (pdfError) {

          Logger.log("PDF generatie fout: " + pdfError.toString());

        }

        // --- DISCORD EMBED MET BEIDE LINKS ---

        const maxSnippetLength = 1000;

        const snippet = plainBody.length > maxSnippetLength

          ? plainBody.substring(0, maxSnippetLength) + "..."

          : plainBody;

        const embedDescription = snippet + (plainBody.length > maxSnippetLength ? "\n\n[Lees meer]" : "");

        const embed = {

          title: subject.substring(0, 256), // Discord title limit: 256 chars

          description: embedDescription.substring(0, 2048), // Discord description limit: 2048 chars

          url: gmailLink,

          color: 0x3498db,

          fields: [

            { name: "Van:", value: from.substring(0, 1024), inline: true },

            { name: "Datum:", value: new Date(message.getDate()).toLocaleString("nl-NL"), inline: true },

            { name: "Gmail:", value: `[Open in Gmail](${gmailLink})`, inline: true }

          ],

          timestamp: new Date().toISOString(),

          footer: { text: "E-mail geforward via Gmail → Discord" }

        };

        if (htmlDriveUrl) {

          embed.fields.push({

            name: "HTML:",

            value: `[Bekijk HTML](${htmlDriveUrl})`,

            inline: true

          });

        }

        if (pdfDriveUrl) {

          embed.fields.push({

            name: "PDF:",

            value: `[Bekijk PDF](${pdfDriveUrl})`,

            inline: true

          });

        }

        if (hasAttachments && attachmentLinks.length > 0) {

          embed.fields.push({

            name: "Bijlagen:",

            value: attachmentLinks.join("\n").substring(0, 1024), // Max 1024 chars per field

            inline: false

          });

        }

        const payload = { embeds: [embed] };

        // --- VERSTUREN NAAR DISCORD MET RETRY LOGICA ---

        const success = sendToDiscord(payload);

        if (success) {

          message.markRead(); // ✅ MARKEER ALLEEN DIT BERICHT

          Logger.log(`✅ E-mail succesvol geforward: ${subject}`);

        } else {

          Logger.log(`❌ E-mail niet verzonden: ${subject} (zie logs voor details)`);

        }

      } catch (error) {

        Logger.log("❌ Fout bij verwerken van e-mail: " + error.toString());

        sendToDiscord({ content: `⚠️ Fout bij ${subject}: ${error.toString().substring(0, 2000)}` });

      }

    });

  });

}

// Test functie

function testForwarding() {

  forwardEmailsToDiscord();

}


Mocht je de gids goed heb gevolgd dan heb je de Folder ID en Discord Webhooklink bij de hand.


Vul deze in bovenaan de code tussen de aanhalingstekens en sla de code op.


Open het Dropdown Menu waar staat getPdfFolder en wijzig deze naar forwardEmailsToDiscord, Dit is de HOOFDFUNCTIE. Druk nu op het knopje Uitvoeren


Volg de stappen en Autoriseer het project. Dit doet google bij ALLE eigen code en projecten, voor Jouw veiligheid, wegens privesituaties kan ik de volgende stappen niet delen maar wel beschrijven: druk op Geavanceerd en dan op ga door naar naamloos project, autoriseer  je account en we kunnen verder.


Als alles goed gaat zie je bij het logboek onderaan dit.


Op discord hoor je nu dit te zien, tenzij je op google beveiligingsmeldingen uit hebt staan.


In Google Drive zie je nu dit.

let op: Google Drive ondersteun GEEN Voorbeeld weergave voor HTML Dat is de reden dat er ook een PDF wordt aangemaakt, de HTML  kan je wel downloaden en bekijken in je webbrowser.

Als laatste gaan we een Trigger instellen.

Druk op Triggers

Druk rechts onder op Trigger toevoegen

Neem deze instellingen over en druk op Opslaan. Dit zorgt ervoor dat het script om de 5 minuten checkt op nieuwe e-mail.

Veel plezier met het gebruik van Gmail en Discord

Related Posts

Gmail met Discord en back-up

Welkom bij deze kleine gids, in deze gids gaan we kijken hoe we het voor elkaar krijgen om nieuwe binnenkomende e-mails (ongelezen) door te sturen als een notificatie in Discord…

Speel Windows Games op je Android Apparaat

Optie 1: Star Star Installeren:Star is een app die je via Github kan vinden en is een open-source fork van winlator. Stap 1: Download Star op je Android telefoon ->…

One thought on “Gmail met Discord en back-up

Geef een reactie