Guide d'intégration du SDK
De l'installation à une gestion d'erreurs prête pour la production, y compris le cas attachment_url_expired (HTTP 410) et sa reprise automatique.
1. Installation
index.html
<!-- 1. Charger le SDK (v1.6.0) -->
<script src="https://votre-app.lovable.app/sdk/flowli-email.js"></script>
<script>
flowli.init("pk_votre_cle_publique");
</script>
<!-- Alternative bundler -->
<!-- npm i --save-dev rien à installer : copiez le fichier ou importez-le en <script type="module"> -->2. Premiers envois
send / sendSMS / pièces jointes
// Envoi simple
const res = await flowli.send("service_smtp", "tpl_welcome", {
to: "client@exemple.com",
first_name: "Ada",
message: "Bienvenue !"
});
console.log(res.status, res.text); // 200 "OK"
// SMS / WhatsApp
await flowli.sendSMS("service_twilio", "tpl_code", { to: "+15145550123", code: "4821" });
// Pièces jointes : { id } | { content_base64 } | { url } signée
await flowli.send("service_smtp", "tpl_facture", { message: "Votre facture" }, null, {
attachments: [
{ id: "att_9f3c21" },
{ filename: "cgv.pdf", content_base64: "JVBERi0x..." },
{ url: "https://votre-app.lovable.app/api/public/v1/att/att_77?exp=...&sig=..." }
]
});3. Gestion complète des erreurs
Copiez ce helper tel quel : il couvre les erreurs de pièces jointes, les refus de validation, les quotas 429 et les pannes temporaires, avec reprise après expiration d'URL signée.
flowli-send.js
/**
* Gestion d'erreurs complète et réutilisable.
* Toute erreur rejetée par le SDK est une FlowliError :
* { name, message, status, code, details, retryable, isAttachmentError, isExpired, attachmentId, expiredAt }
*/
async function envoyerAvecGestionErreurs(payload) {
const MAX_RETRIES = 3;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
return await flowli.send(
payload.serviceId,
payload.templateId,
payload.params,
null,
{ attachments: payload.attachments }
);
} catch (err) {
// 1) Cas prioritaire : URL signée de pièce jointe expirée (HTTP 410)
if (flowli.isExpiredAttachmentError(err)) {
console.warn("URL expirée", err.attachmentId, err.expiredAt, err.message);
// Le SDK tente déjà un refresh automatique une fois. Si on arrive ici,
// on régénère explicitement puis on rejoue une dernière fois.
const refreshed = await flowli.refreshExpiredAttachments(payload.attachments);
if (refreshed.changed) {
payload.attachments = refreshed.attachments;
continue; // rejoue la boucle avec les nouvelles URLs
}
// Le fichier a été purgé du stockage : on ne peut pas rejouer tel quel.
throw new Error(
"Pièce jointe indisponible (" + err.attachmentId + ") — ré-uploadez le fichier."
);
}
// 2) Autres erreurs de pièces jointes : définitives, inutile de réessayer
if (flowli.isAttachmentError(err)) {
console.error("Pièce jointe refusée:", err.code, err.message, err.details);
throw err; // ex. attachment_too_large, attachment_type_blocked, attachment_not_found
}
// 3) Validation / autorisation : afficher à l'utilisateur, ne pas réessayer
if (err.status === 401 || err.status === 403 || err.status === 422) {
console.error("Requête refusée:", err.status, err.code, err.message);
throw err; // clé invalide, origine CORS non autorisée, destinataire invalide
}
// 4) Quotas et pannes temporaires : backoff exponentiel
if (err.retryable && attempt < MAX_RETRIES) {
const waitMs = err.status === 429
? (Number(err.details.retry_after) || 2 ** attempt) * 1000
: 2 ** attempt * 500;
console.warn("Nouvel essai dans", waitMs, "ms —", err.code || err.status);
await new Promise((r) => setTimeout(r, waitMs));
continue;
}
throw err;
}
}
}4. Régénérer une URL signée
POST /api/public/v1/att/refresh via le SDK
// Régénération manuelle d'URLs signées expirées
const { attachments, changed, failed } = await flowli.refreshExpiredAttachments(myAttachments);
if (failed.length) console.warn("Non régénérables (purgés) :", failed);
// Ou par identifiants
const out = await flowli.refreshAttachmentUrls(["att_9f3c21", "att_77"], { ttl: 3600 });
console.log(out.attachments[0].url);5. Valider avant d'envoyer (dry-run)
Validation sans envoi ni facturation
// Valider clés, template, destinataires et pièces jointes SANS envoyer
const report = await flowli.send("service_smtp", "tpl_facture", params, null, {
attachments,
dryRun: true
});
if (!report.ok) {
report.errors.forEach((e) => console.error(e.code, e.message));
} else {
console.log(report.preview.subject, report.attachments, report.estimate);
}
// Pré-validation 100% locale (aucun appel réseau)
const check = flowli.validateAttachments(attachments, { channel: "email" });
if (!check.ok) alert(check.message); // ex. "Fichier trop volumineux (max 8 Mo)"6. Référence des codes d'erreur
| Code | HTTP | Action recommandée |
|---|---|---|
| attachment_url_expired | 410 | URL signée périmée avant l'envoi → régénérer via refreshExpiredAttachments() puis rejouer. |
| attachment_expired | 410 | Le fichier stocké a dépassé son TTL → ré-uploader. |
| attachment_not_found | 404 | Identifiant inconnu pour ce compte → vérifier l'id. |
| attachment_url_invalid | 400 | Signature altérée ou URL non https → ne pas rejouer. |
| attachment_too_large | 413 | Dépasse la taille max du canal (8 Mo email, 5 Mo MMS). |
| attachments_too_many | 422 | Trop de fichiers pour la politique du compte. |
| attachment_type_blocked | 415 | Extension/MIME interdit (exécutables, archives selon politique). |
| rate_limited | 429 | Quota canal dépassé → attendre details.retry_after puis réessayer. |
| invalid_public_key | 401 | Clé publique inconnue ou révoquée. |
| origin_not_allowed | 403 | Domaine appelant absent de la whitelist CORS du service. |
Bonnes pratiques
- Ne jamais exposer une clé
sk_dans le navigateur : uniquementpk_. - Préférer
{ id }à{ url }pour les pièces jointes stockées : aucun risque d'expiration de signature. - Écouter le webhook
attachment.url_expiredpour être alerté côté serveur. - Utiliser
dryRunen CI pour valider vos templates avant déploiement.