Jak automatycznie pobrać faktury z Gmaila do Dysku Google – darmowy skrypt Apps Script

Każdy, kto prowadzi biznes, zna ten problem: dziesiątki maili z fakturami ginących w gąszczu innych wiadomości, a znalezienie konkretnego pliku PDF staje się frustrującym wyzwaniem. Google niestety nie daje możliwości automatycznego zapisania załączników z konkretnych maili.

Postanowiłem rozwiązać ten problem raz na zawsze – stworzyłem prosty, ale potężny skrypt, który automatycznie wyszukuje i pobiera załączniki prosto z Gmaila na Twój Google Drive.

Dlaczego nie zrobiłem go jako pełna aplikacja tylko skrypt? Bo dzięki temu nie musisz się obawiać, że twoje dane w niekontrolowany sposób wyciekną poza serwery Google. Jego działanie odbywa się w 100% wewnątrz infrastruktury Google i twojego konta.

Co to jest Google Apps Script?

Google Apps Script (dostępne pod adresem script.google.com) to lekki, chmurowy język skryptowy od Google. Umożliwia on:

  • Automatyzację zadań w usługach Google, takich jak Gmail, Arkusze, Dysk czy Dokumenty.
  • Tworzenie niestandardowych dodatków i menu w Arkuszach czy Dokumentach.
  • Łączenie się z zewnętrznymi API poprzez zapytania HTTP.
  • Ustalanie wyzwalaczy czasowych oraz reagowanie na zdarzenia (nowe wiersze w arkuszu, odpowiedzi w formularzu itp.).
  • Publikację prostych web-aplikacji z interfejsem HTML/CSS/JS.

Dzięki Apps Script możesz w kilka minut stworzyć narzędzie, które za Ciebie wykona powtarzalne czynności – bez instalowania czegokolwiek lokalnie, wprost w przeglądarce.

Jak działa nasz skrypt?

  • Przeszukuje skrzynkę Gmail według ustawionych kryteriów: nadawca, zakres dat, słowo kluczowe w tytule.
  • Wybiera tylko pliki PDF, których nazwa zawiera określone słowo kluczowe.
  • Zapisuje je w dedykowanym folderze na Dysku Google.
  • Obsługuje duże zbiory wiadomości dzięki paginacji i automatycznemu wznawianiu pracy.

Jak zacząć korzystać ze skryptu?

  1. Wejdź na script.google.com (albo krótko: script.new) i utwórz nowy projekt.
  2. Wklej poniższy kod do pliku Code.gs / (Kod.gs).
  3. Dostosuj zmienne konfiguracyjne na początku skryptu.
  4. Zapisz skrypt i uruchom ręcznie lub dodaj wyzwalacz czasowy (możesz go skonfigurować także do działania automatycznego, co jakiś czas).

Konfiguracja zmiennych:

  • SENDER: adres e-mail nadawcy, od którego mają być pobierane załączniki.
  • SUBJECT_KEYWORD: słowo kluczowe w tytule maila.
  • FILE_KEYWORD: słowo kluczowe w nazwie pliku PDF.
  • START_DATEEND_DATE: zakres dat w formacie YYYY/MM/DD (puste = brak ograniczeń).
  • FOLDER_NAME: nazwa folderu na Dysku Google, do którego zapisane zostaną pliki.
  • BATCH_SIZE: liczba wątków przetwarzanych w jednej partii (zalecane 100).

Przykładowe zastosowania:

  • Automatyczne pobieranie comiesięcznych faktur od dostawców.
  • Archiwizacja raportów PDF od partnerów.
  • Regularne kopie ważnych dokumentów finansowych.

Masz pytania lub potrzebujesz pomocy? Chętnie pomogę!

PS. Mam masę takich skryptów, które bardzo ułatwiają codzienne życie. Jeśli to dla ciebie interesujący temat, to daj znać - może wystawię je wszystkie w formie repozytorium.


/*
 * Funkcja: saveAttachments
 * Automatycznie pobiera załączniki z Gmaila według konfiguracji.
 * Zapisuje tylko pliki PDF zawierające zadane słowo kluczowe.
 * Obsługuje zakres dat, paginację i wznawia pracę od ostatniego batcha.
 */
function saveAttachments() {
  // ====== KONFIGURACJA ======
  const SENDER           = 'sender@example.com';    // <-- Nadawca e-mail
  const SUBJECT_KEYWORD  = 'invoice';               // <-- Słowo w tytule maila
  const FILE_KEYWORD     = 'invoice';               // <-- Słowo w nazwie pliku
  const START_DATE       = '';                      // <-- after: YYYY/MM/DD lub ''
  const END_DATE         = '';                      // <-- before: YYYY/MM/DD lub ''
  const FOLDER_NAME      = `Attachments-${SENDER}`; // <-- Folder na Dysku Google
  const BATCH_SIZE       = 100;                     // <-- Liczba wątków na partię
  const PROP_CURSOR      = 'attachmentCursor';      // <-- Klucz kursora w Properties

  // ====== Budowanie zapytania Gmail ======
  let queryParts = [
    `from:${SENDER}`,
    `subject:${SUBJECT_KEYWORD}`,
    'has:attachment'
  ];
  if (START_DATE) queryParts.push(`after:${START_DATE}`);
  if (END_DATE)   queryParts.push(`before:${END_DATE}`);
  const QUERY = queryParts.join(' ');
  Logger.log('Zapytanie: %s', QUERY);

  // ====== Przygotowanie folderu ======
  const parent = DriveApp.getFoldersByName(FOLDER_NAME).hasNext()
    ? DriveApp.getFoldersByName(FOLDER_NAME).next()
    : DriveApp.createFolder(FOLDER_NAME);
  Logger.log('Używany folder: %s (ID: %s)', parent.getName(), parent.getId());

  // ====== Ustawienia paginacji ======
  const props = PropertiesService.getScriptProperties();
  let cursor = Number(props.getProperty(PROP_CURSOR) || 0);
  Logger.log('Start kursora: %s', cursor);

  const startTime = new Date();
  const threads = GmailApp.search(QUERY, cursor, BATCH_SIZE);
  Logger.log('Wątki w tej partii: %s (kursor: %s)', threads.length, cursor);

  let batchMessages = 0;
  let batchAttachments = 0;

  threads.forEach(thread => {
    thread.getMessages().forEach(msg => {
      const msgId = msg.getId();
      const subj = msg.getSubject();
      const msgDate = msg.getDate();
      batchMessages++;
      msg.getAttachments().forEach(att => {
        const fileName = att.getName();
        const lower = fileName.toLowerCase();
        if (lower.includes(FILE_KEYWORD.toLowerCase()) && lower.endsWith('.pdf')) {
          if (!parent.getFilesByName(fileName).hasNext()) {
            parent.createFile(att.copyBlob())
              .setDescription(`ID wiadomości: ${msgId} | Data: ${msgDate} | Temat: ${subj}`);
            batchAttachments++;
            Logger.log('Zapisano: %s | ID wiadomości: %s | Data: %s | Temat: %s',
                       fileName, msgId, msgDate, subj);
          } else {
            Logger.log('Pominięto duplikat: %s | ID wiadomości: %s | Data: %s',
                       fileName, msgId, msgDate);
          }
        } else {
          Logger.log('Pominięto plik: %s | ID wiadomości: %s | Data: %s',
                     fileName, msgId, msgDate);
        }
      });
    });
  });

  const endTime = new Date();
  Logger.log('Podsumowanie partii: przetworzono wiadomości: %s, zapisano załączników: %s, czas: %s s',
             batchMessages, batchAttachments, (endTime - startTime) / 1000);

  // ====== Logika wznawiania ======
  if (threads.length === BATCH_SIZE) {
    const nextCursor = cursor + BATCH_SIZE;
    props.setProperty(PROP_CURSOR, nextCursor.toString());
    ScriptApp.getProjectTriggers()
      .filter(t => t.getHandlerFunction() === 'saveAttachments')
      .forEach(t => ScriptApp.deleteTrigger(t));
    ScriptApp.newTrigger('saveAttachments')
      .timeBased()
      .after(1 * 60 * 1000)
      .create();
    Logger.log('Zaplanowano kolejną partię od kursora %s', nextCursor);
  } else {
    props.deleteProperty(PROP_CURSOR);
    ScriptApp.getProjectTriggers()
      .filter(t => t.getHandlerFunction() === 'saveAttachments')
      .forEach(t => ScriptApp.deleteTrigger(t));
    Logger.log('Wszystko wykonane.');
  }
}
Ładowanie komentarzy...
Pomyślnie zapisałeś się do Jesion: Uniting Perspectives on Society, Science, Technology & Art
Świetnie! Następnie przejdź do kasy, aby uzyskać pełny dostęp do wszystkich treści premium.
Błąd! Nie można się zarejestrować. Niepoprawny link.
Witamy ponownie! Pomyślnie się zalogowałeś.
Błąd! Nie można się zalogować. Proszę spróbować ponownie.
Sukces! Twoje konto zostało w pełni aktywowane, masz teraz dostęp do wszystkich treści.
Błąd! Operacja pobrania środków przez Stripe nie powiodła się.
Sukces! Twoje dane rozliczeniowe zostały zaktualizowane.
Błąd! Aktualizacja informacji rozliczeniowych nie powiodła się.