danny-avila/LibreChat · error · Error

Failed to send email via Mailgun

Error message

Failed to send email via Mailgun

What it means

Thrown by sendEmailViaMailgun when the axios POST to `${mailgunHost}/v3/${mailgunDomain}/messages` fails. The underlying axios error is formatted by logAxiosError and re-thrown as a single Error whose message includes the HTTP status and Mailgun response body. Causes range from auth failure to rejected recipients to network errors.

Source

Thrown at api/server/utils/sendEmail.js:47

  const formData = new FormData();
  formData.append('from', from);
  formData.append('to', to);
  formData.append('subject', subject);
  formData.append('html', html);
  formData.append('o:tracking-clicks', 'no');

  try {
    const response = await axios.post(`${mailgunHost}/v3/${mailgunDomain}/messages`, formData, {
      headers: {
        ...formData.getHeaders(),
        Authorization: `Basic ${Buffer.from(`api:${mailgunApiKey}`).toString('base64')}`,
      },
    });

    return response.data;
  } catch (error) {
    throw new Error(logAxiosError({ error, message: 'Failed to send email via Mailgun' }));
  }
};

/**
 * Sends an email using SMTP via Nodemailer.
 *
 * @async
 * @function sendEmailViaSMTP
 * @param {Object} params - The parameters for sending the email.
 * @param {Object} params.transporterOptions - The transporter configuration options.
 * @param {Object} params.mailOptions - The email options.
 * @returns {Promise<Object>} - A promise that resolves to the info object of the sent email.
 */
const sendEmailViaSMTP = async ({ transporterOptions, mailOptions }) => {
  const transporter = nodemailer.createTransport(transporterOptions);
  return await transporter.sendMail(mailOptions);
};

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Read the full logAxiosError message to obtain the exact HTTP status and Mailgun error code.
  2. For 401, regenerate the API key in Mailgun and update MAILGUN_API_KEY.
  3. For 400/404, confirm MAILGUN_DOMAIN is a verified domain on that account and that recipients are well-formed.
  4. For EU accounts, set MAILGUN_HOST to https://api.eu.mailgun.net.
  5. Clear suppressions (bounces/complaints) for the recipient in the Mailgun dashboard if delivery is blocked.

Example fix

// before
await sendEmailViaMailgun({ to, from, subject, html });

// after (structured failure for the caller)
try {
  await sendEmailViaMailgun({ to, from, subject, html });
} catch (err) {
  logger.error('Mailgun send failed:', err.message);
  throw new Error(`Email delivery failed; please retry. (${err.message})`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.MAILGUN_API_KEY || !process.env.MAILGUN_DOMAIN) {
  throw new Error('Mailgun API key and domain are required');
}
if (!to || !from || !subject || !html) {
  throw new Error('to, from, subject, and html are required to send email');
}

Try / catch

try {
  await sendEmailViaMailgun({ to, from, subject, html });
} catch (err) {
  if (err.message.startsWith('Failed to send email via Mailgun')) {
    logger.error('Mailgun delivery failed:', err.message);
    throw new Error('Email delivery failed; please retry.');
  }
  throw err;
}

Prevention

When it happens

Trigger: HTTP 401 from a wrong/revoked MAILGUN_API_KEY; 400 from a malformed recipient or bad domain; 404 when MAILGUN_DOMAIN does not match the key's account; MAILGUN_HOST pointing at the wrong region (EU vs US); TLS/DNS/network failure; rate limiting from Mailgun.

Common situations: Rotated the API key in the Mailgun dashboard without updating env; domain not verified in Mailgun; recipient on a suppression/bounce list; EU account using the default api.mailgun.net host.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/62b64c2a178bba38. Report an issue: GitHub.