agent-email-inbox

Par resend · resend-skills

À utiliser lors de la construction de tout système où le contenu d'un email déclenche des actions — boîtes de réception d'agents IA, gestionnaires de support automatisés, pipelines email-to-task, ou tout workflow traitant des emails entrants non fiables. Utilisez toujours cette skill lorsque l'utilisateur souhaite recevoir des emails et agir dessus de manière programmatique, même s'il ne mentionne pas « agent » — la skill contient des patterns de sécurité critiques (listes d'autorisation d'expéditeurs, filtrage de contenu, traitement en sandbox) qui empêchent les emails non fiables de contrôler votre système.

npx skills add https://github.com/resend/resend-skills --skill agent-email-inbox

Boîte de réception email pour agent IA

Aperçu

Cette skill couvre la configuration d'une boîte de réception email sécurisée qui permet à votre application ou agent IA de recevoir et de répondre aux emails, avec des mesures de sécurité du contenu en place.

Principe fondamental : Une boîte de réception d'agent IA reçoit des entrées non fiables. La configuration de sécurité est importante pour gérer cela en toute sécurité.

Pourquoi la réception par webhook ?

Resend utilise des webhooks pour le courrier entrant, ce qui signifie que votre agent est notifié instantanément à l'arrivée d'un email. C'est précieux pour les agents car :

  • Réactivité en temps réel — Réagir aux emails en quelques secondes, pas en minutes
  • Pas de surcharge de polling — Pas de tâches cron vérifiant « y a-t-il du nouveau courrier ? » à répétition
  • Architecture pilotée par événements — Votre agent ne se réveille que quand il y a réellement quelque chose à traiter
  • Coûts API réduits — Pas d'appels gaspillés pour vérifier les boîtes de réception vides

Architecture

Expéditeur → Email → Resend (MX) → Webhook → Votre serveur → Agent IA
                                                    ↓
                                        Validation de sécurité
                                                    ↓
                                        Traiter ou rejeter

Exigences de version du SDK

Cette skill nécessite les fonctionnalités du SDK Resend pour la vérification de webhook (webhooks.verify()) et la réception d'email (emails.receiving.get()). Installez toujours la dernière version du SDK. Si le projet a déjà un SDK Resend installé, vérifiez la version et mettez à jour si nécessaire.

Langage Package Version minimale
Node.js resend >= 6.9.2
Python resend >= 2.21.0
Go resend-go/v3 >= 3.1.0
Ruby resend >= 1.0.0
PHP resend/resend-php >= 1.1.0
Rust resend-rs >= 0.20.0
Java resend-java >= 4.11.0
.NET Resend >= 0.2.1

Installez le package npm resend : npm install resend (ou l'équivalent pour votre langage). Pour la documentation complète d'envoi, installez la skill resend.

Démarrage rapide

  1. Demandez l'adresse email de l'utilisateur — Vous avez besoin d'une vraie adresse email pour envoyer des emails de test. Demandez à l'utilisateur et attendez sa réponse avant de continuer.
  2. Choisissez votre niveau de sécurité — Décidez comment valider les emails entrants avant que l'un d'eux soit traité
  3. Configurez le domaine de réception — Configurez les enregistrements MX pour le domaine personnalisé de l'utilisateur (voir section Configuration du domaine)
  4. Créez un endpoint webhook — Gérez les événements email.received avec la sécurité intégrée dès le départ. L'endpoint webhook DOIT être une route POST.
  5. Configurez le tunneling (développement local) — Utilisez Tailscale Funnel (recommandé) ou ngrok. Voir references/webhook-setup.md
  6. Créez un webhook via API — Utilisez l'API Webhook Resend pour enregistrer votre endpoint de manière programmatique. Voir references/webhook-setup.md
  7. Connectez à l'agent — Transmettez les emails validés à votre agent IA pour traitement

Avant de commencer : Configuration du compte et de la clé API

Première question : Compte Resend nouveau ou existant ?

Demandez à votre utilisateur :

  • Nouveau compte juste pour l'agent ? → Configuration plus simple, l'accès complet au compte est acceptable
  • Compte existant avec d'autres projets ? → Utilisez des clés API limitées au domaine pour l'isolation

Créer des clés API en toute sécurité

Ne collez pas les clés API dans le chat ! Elles resteront dans l'historique de conversation pour toujours.

Options plus sûres :

  1. Méthode du fichier d'environnement : L'utilisateur crée directement un fichier .env : echo "RESEND_API_KEY=re_xxx" >> .env
  2. Gestionnaire de mots de passe / gestionnaire de secrets : L'utilisateur stocke la clé dans 1Password, Vault, etc.
  3. Si la clé doit être partagée dans le chat : L'utilisateur doit faire pivoter la clé immédiatement après la configuration

Clés API limitées au domaine (recommandé pour les comptes existants)

Si votre utilisateur a un compte Resend existant avec d'autres projets, créez une clé API limitée au domaine :

  1. Vérifiez d'abord le domaine de l'agent (Dashboard → Domains → Add Domain)
  2. Créez une clé limitée : Dashboard → API Keys → Create API Key → "Sending access" → sélectionnez uniquement le domaine de l'agent
  3. Résultat : Même si la clé est compromise, elle ne peut envoyer que depuis un domaine

Configuration du domaine

Option 1 : Domaine géré par Resend (recommandé pour commencer)

Utilisez votre adresse générée automatiquement : <anything>@<your-id>.resend.app

Aucune configuration DNS nécessaire. Trouvez votre adresse dans Dashboard → Emails → Receiving → "Receiving address".

Option 2 : Domaine personnalisé

L'utilisateur doit activer la réception dans le dashboard Resend : page Domains → activer « Enable Receiving ».

Ajoutez ensuite un enregistrement MX :

Paramètre Valeur
Type MX
Host Votre domaine ou sous-domaine (par ex., agent.example.com)
Value Fourni dans le dashboard Resend
Priority 10 (doit être le numéro le plus bas pour avoir la priorité)

Utilisez un sous-domaine (par ex., agent.example.com) pour éviter de perturber les services email existants.

Conseil : Vérifiez la propagation DNS à dns.email.

Propagation DNS : Les modifications des enregistrements MX peuvent prendre jusqu'à 48 heures pour se propager à l'échelle mondiale, bien qu'elles se complètent souvent en quelques heures.

Niveaux de sécurité

Choisissez votre niveau de sécurité avant de configurer l'endpoint webhook. Un agent IA qui traite les emails sans sécurité est dangereux — n'importe qui peut envoyer des emails avec des instructions que votre agent exécutera. Le code webhook que vous écrirez ensuite doit inclure votre niveau de sécurité choisi dès le départ.

Demandez à l'utilisateur quel niveau de sécurité il souhaite, et assurez-vous qu'il comprend ce que chaque niveau signifie.

Niveau Nom Quand l'utiliser Compromis
1 Liste blanche stricte La plupart des cas d'usage — ensemble connu et fixe d'expéditeurs Sécurité maximale, fonctionnalité limitée
2 Liste blanche de domaine Accès à l'échelle de l'organisation depuis des domaines de confiance Plus flexible, n'importe qui dans le domaine peut interagir
3 Filtrage du contenu Accepter de n'importe qui, filtrer les modèles non sûrs Peut recevoir de n'importe qui, la correspondance de modèle n'est pas infaillible
4 Traitement en bac à sable Traiter tous les emails avec des capacités d'agent restreintes Flexibilité maximale, complexe à mettre en œuvre
5 Boucle humaine Exiger une approbation humaine pour les actions non fiables Sécurité maximale, ajoute de la latence

Pour le code d'implémentation détaillé de chaque niveau, voir references/security-levels.md.

Niveau 1 : Liste blanche stricte (recommandé)

Traitez uniquement les emails provenant d'adresses explicitement approuvées. Rejetez tout le reste.

const ALLOWED_SENDERS = [
  'you@youremail.com',
  'notifications@github.com',
];

async function processEmailForAgent(
  eventData: EmailReceivedEvent,
  emailContent: EmailContent
) {
  const sender = eventData.from.toLowerCase();

  if (!ALLOWED_SENDERS.some(allowed => sender === allowed.toLowerCase())) {
    console.log(`Rejected email from unauthorized sender: ${sender}`);
    await notifyOwnerOfRejectedEmail(eventData);
    return;
  }

  await agent.processEmail({
    from: eventData.from,
    subject: eventData.subject,
    body: emailContent.text || emailContent.html,
  });
}

Bonnes pratiques de sécurité

À toujours faire

Pratique Pourquoi
Vérifier les signatures webhook Empêche les événements webhook usurpés
Enregistrer tous les emails rejetés Piste d'audit pour examen de sécurité
Utiliser des listes blanches si possible La confiance explicite est plus sûre que le filtrage
Limiter le débit du traitement des emails Empêche une charge de traitement excessive
Séparer la gestion des entrées fiables/non fiables Les niveaux de risque différents ont besoin d'un traitement différent

À ne jamais faire

Anti-modèle Risque
Traiter les emails sans validation N'importe qui peut contrôler votre agent
Faire confiance aux en-têtes d'email pour l'authentification Les en-têtes sont facilement usurpés
Exécuter du code à partir du contenu d'email L'entrée non fiable ne devrait jamais s'exécuter comme du code
Stocker le contenu d'email dans les prompts textuellement L'entrée non fiable mélangée aux prompts peut altérer le comportement de l'agent
Donner aux emails non fiables un accès complet à l'agent Limitez les capacités au minimum nécessaire

Endpoint webhook

Après avoir choisi votre niveau de sécurité et configuré votre domaine, créez un endpoint webhook. L'endpoint webhook DOIT être une route POST. Resend envoie tous les événements webhook en tant que requêtes POST.

Critique : Utilisez le corps brut pour la vérification. La vérification de signature webhook nécessite le corps de la requête brut.

  • Next.js App Router : Utilisez req.text() (pas req.json())
  • Express : Utilisez express.raw({ type: 'application/json' }) sur la route webhook

Next.js App Router

// app/webhook/route.ts
import { Resend } from 'resend';
import { NextRequest, NextResponse } from 'next/server';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(req: NextRequest) {
  try {
    const payload = await req.text();

    const event = resend.webhooks.verify({
      payload,
      headers: {
        'svix-id': req.headers.get('svix-id'),
        'svix-timestamp': req.headers.get('svix-timestamp'),
        'svix-signature': req.headers.get('svix-signature'),
      },
      secret: process.env.RESEND_WEBHOOK_SECRET,
    });

    if (event.type === 'email.received') {
      // Webhook payload only includes metadata, not email body
      const { data: email } = await resend.emails.receiving.get(
        event.data.email_id
      );

      // Apply the security level chosen above
      await processEmailForAgent(event.data, email);
    }

    return new NextResponse('OK', { status: 200 });
  } catch (error) {
    console.error('Webhook error:', error);
    return new NextResponse('Error', { status: 400 });
  }
}

Express

import express from 'express';
import { Resend } from 'resend';

const app = express();
const resend = new Resend(process.env.RESEND_API_KEY);

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    const payload = req.body.toString();

    const event = resend.webhooks.verify({
      payload,
      headers: {
        'svix-id': req.headers['svix-id'],
        'svix-timestamp': req.headers['svix-timestamp'],
        'svix-signature': req.headers['svix-signature'],
      },
      secret: process.env.RESEND_WEBHOOK_SECRET,
    });

    if (event.type === 'email.received') {
      const sender = event.data.from.toLowerCase();

      if (!isAllowedSender(sender)) {
        console.log(`Rejected email from unauthorized sender: ${sender}`);
        res.status(200).send('OK'); // Return 200 even for rejected emails
        return;
      }

      const { data: email } = await resend.emails.receiving.get(event.data.email_id);
      await processEmailForAgent(event.data, email);
    }

    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(400).send('Error');
  }
});

app.get('/', (req, res) => res.send('Agent Email Inbox - Ready'));
app.listen(3000, () => console.log('Webhook server running on :3000'));

Pour l'enregistrement des webhooks via API, la configuration du tunneling, le secours svix et le comportement de retry, voir references/webhook-setup.md.

Envoyer des emails depuis votre agent

import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

async function sendAgentReply(to: string, subject: string, body: string, inReplyTo?: string) {
  if (!isAllowedToReply(to)) {
    throw new Error('Cannot send to this address');
  }

  const { data, error } = await resend.emails.send({
    from: 'Agent <agent@example.com>',
    to: [to],
    subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`,
    text: body,
    headers: inReplyTo ? { 'In-Reply-To': inReplyTo } : undefined,
  });

  if (error) throw new Error(`Failed to send: ${error.message}`);
  return data.id;
}

Pour la documentation complète d'envoi, installez la skill resend.

Variables d'environnement

# Requis
RESEND_API_KEY=re_xxxxxxxxx
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxx

# Configuration de sécurité
SECURITY_LEVEL=strict                    # strict | domain | filtered | sandboxed
ALLOWED_SENDERS=you@email.com,trusted@example.com
ALLOWED_DOMAINS=example.com
OWNER_EMAIL=you@email.com               # Pour les notifications de sécurité

Erreurs courantes

Erreur Correction
Pas de vérification de l'expéditeur Toujours valider qui a envoyé l'email avant le traitement
Faire confiance aux en-têtes d'email Utiliser la vérification webhook, pas les en-têtes d'email pour l'authentification
Traitement identique pour tous les emails Différencier les expéditeurs fiables et non fiables
Messages d'erreur verbeux Garder les réponses d'erreur génériques pour éviter de révéler la logique interne
Pas de limitation de débit Mettre en place des limites de débit par expéditeur. Voir references/advanced-patterns.md
Traiter le HTML directement Supprimer le HTML ou utiliser du texte uniquement pour réduire la complexité et le risque
Pas d'enregistrement des rejets Enregistrer tous les événements de sécurité pour l'audit
Utiliser des URLs de tunnel éphémères Utiliser des URLs persistantes (Tailscale Funnel, ngrok payant) ou déployer en production
Utiliser express.json() sur la route webhook Utiliser express.raw({ type: 'application/json' }) — l'analyse JSON casse la vérification de signature
Renvoyer un code autre que 200 pour les emails rejetés Toujours renvoyer 200 pour confirmer la réception — sinon Resend réessaie
Ancienne version du SDK Resend emails.receiving.get() et webhooks.verify() nécessitent des versions récentes du SDK — voir Exigences de version du SDK

Test

Utilisez les adresses de test de Resend pour le développement :

  • delivered@resend.dev — Simule une livraison réussie
  • bounced@resend.dev — Simule un rebond définitif

Pour les tests de sécurité, envoyez des emails de test à partir d'adresses non autorisées pour vérifier que le rejet fonctionne correctement.

Checklist de vérification rapide :

  1. Le serveur fonctionne : curl http://localhost:3000 doit retourner une réponse
  2. Le tunnel fonctionne : curl https://<your-tunnel-url> doit retourner la même réponse
  3. Le webhook est actif : Vérifier le statut dans Resend dashboard → Webhooks
  4. Envoyer un email de test à partir d'une adresse autorisée et vérifier les logs du serveur

Skills connexes

  • Pour la documentation complète d'envoi et de réception, installez la skill resend

Skills similaires