appscript.dev
Automation Intermediate Gmail

Auto-translate incoming foreign-language emails

Append an English translation as a draft reply so the team can respond fluently in the original language.

Published Jan 27, 2026

Northwind’s support@ inbox gets the occasional email in a language nobody on the desk reads. The current routine is to paste it into a translation site, read the gist, then write a reply — fiddly, and it interrupts whoever picked up the thread. Worse, the translation is gone the moment the tab is closed, so the next person to touch the thread starts from scratch.

This script does the lookup once, up front. It scans support@ for unread non-English mail, detects the language, translates the body into English, and attaches that translation as a draft reply on the thread. The draft is an internal note for context — the team still writes the real reply themselves — and the thread is labelled so it is never translated twice.

What you’ll need

  • A Gmail account (or delegated mailbox) where support@ mail carries the support label.
  • Permission to add a time-driven trigger and create drafts on those threads.
  • Nothing else — the script creates the support/translated label itself.

The script

// The label applied to incoming support mail.
const SUPPORT_LABEL = 'support';

// The label this script adds once a thread has been translated.
const TRANSLATED_LABEL = 'support/translated';

/**
 * Finds unread, non-English support threads, translates the first
 * message into English, and attaches it as an internal-only draft reply.
 */
function translateIncoming() {
  // 1. Find unread support threads that have not been translated yet.
  const query =
    'label:' + SUPPORT_LABEL +
    ' is:unread -label:' + TRANSLATED_LABEL;
  const threads = GmailApp.search(query);
  if (!threads.length) {
    Logger.log('No untranslated support threads — nothing to do.');
    return;
  }

  // 2. Get the "translated" label, creating it on first run.
  const translated =
    GmailApp.getUserLabelByName(TRANSLATED_LABEL) ||
    GmailApp.createLabel(TRANSLATED_LABEL);

  let count = 0;

  // 3. Handle each thread in turn.
  for (const t of threads) {
    const msg = t.getMessages()[0];
    const body = msg.getPlainBody();

    // 4. Detect the language; skip anything already in English.
    const lang = LanguageApp.detect(body);
    if (!lang || lang === 'en') {
      t.addLabel(translated); // mark it so we don't re-check next run
      continue;
    }

    // 5. Translate to English and attach it as an internal draft note.
    const english = LanguageApp.translate(body, lang, 'en');
    t.createDraftReply(
      '[Internal note — auto-translated from ' + lang + ']\n\n' + english
    );

    // 6. Label the thread so it is not translated again.
    t.addLabel(translated);
    count++;
  }

  Logger.log('Translated ' + count + ' thread(s).');
}

How it works

  1. translateIncoming searches Gmail for threads that carry the support label, are unread, and do not carry support/translated — so each thread is only ever handled once.
  2. It looks up the support/translated label, creating it the first time the script runs.
  3. For each thread it reads the plain-text body of the first message — plain text keeps the translation clean of HTML markup.
  4. It runs LanguageApp.detect on the body. If the language comes back as English (or cannot be detected), it labels the thread and skips it, so an English email is not pointlessly re-checked every run.
  5. For genuinely foreign mail it calls LanguageApp.translate to English and creates a draft reply on the thread, prefixed with a clear [Internal note] marker so nobody mistakes it for an outgoing message.
  6. It adds the support/translated label, taking the thread out of scope for future runs.

Example run

An email lands in support@, labelled support, unread:

Subject: Problema con la factura Hola, no he recibido la factura del mes pasado. ¿Pueden reenviarla?

After the next run, the thread has a draft reply waiting:

[Internal note — auto-translated from es]

Hello, I have not received last month’s invoice. Can you resend it?

Whoever picks up the thread reads the draft for context, writes the real reply in Spanish, and deletes or overwrites the note. The thread now carries support/translated and is skipped from then on.

Trigger it

Run this on a time-driven trigger so translations are ready before anyone opens the inbox:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add Trigger.
  3. Choose translateIncoming, event source Time-driven, type Minutes timer, and Every 15 minutes.
  4. Save and approve the authorisation prompt.

Watch out for

  • LanguageApp.translate is fine for understanding an incoming message but rough for outgoing copy. Pair it with Auto-label and route emails by language so the actual reply comes from a native speaker.
  • The draft note is internal context only. Make sure the team knows to delete or replace it before sending — a stray [Internal note] block in a customer reply is embarrassing.
  • Only the first message of each thread is translated. A long back-and-forth where later replies switch language will not get a fresh translation, because the thread is already labelled support/translated.
  • LanguageApp.detect works on the whole body, so a message mixing languages — or one with a long English signature under a short foreign request — can be misdetected. Plain-text bodies and a little quoted-text trimming help.
  • Both LanguageApp and Gmail draft creation are subject to daily quotas. A normal support volume is well within them; a sudden flood of mail is not.

Related