SMS

Envoi de messages

Envoyez un SMS depuis un numéro qui vous appartient. L’API vérifie la destination canadienne et le consentement LCAP avant que quoi que ce soit n’atteigne l’opérateur.

Envoi de SMS

Envoyez un message en utilisant un numéro provisionné. L'API valide le numéro de destination canadien, vérifie le consentement LCAP avant la livraison.

// "from" is one of your HonkIO numbers; "to" is a real number you hold consent for.
// Reuse the idempotency key on a retry: it returns the original message instead of sending and charging again.
const { data, error } = await honkio.messages.send({
  from: '+1416XXXXXXX',
  to: '+1613XXXXXXX',
  body: 'Hello from HonkIO! 🇨🇦',
}, { idempotencyKey: 'order-1042-shipped' })
if (error) throw new Error(`${error.name}: ${error.message}`)
console.log(data.id, data.status)

Un contact (POST /v1/contacts) prend un phone_number, un email, ou les deux. Les groupes, les diffusions et les listes d’autorisation ou de blocage utilisent les contacts qui ont un numéro de téléphone ; en ajouter un qui n’en a pas est refusé avec 422 CONTACT_HAS_NO_PHONE, et effacer le numéro d’un contact membre d’un groupe ou d’une liste avec 409 CONTACT_IN_USE. Les contacts, groupes et listes sont tenus séparément pour chaque mode de clé : une clé de test ne voit et ne modifie que ceux de test.

Vous envoyez le même message à beaucoup de gens? Consultez Envoyer à une liste pour le rythme d’un numéro, les diffusions à un groupe et la gestion des refus 429.

Programmer un message

Ajoutez scheduled_at pour envoyer plus tard, par exemple un rappel de rendez-vous la veille : une date et heure ISO 8601 avec un décalage, de 1 minute à 30 jours à l’avance. La réponse est un 201 avec le statut SCHEDULED et scheduled_at, et un envoi réel est facturé tout de suite (son estimated_send_at est son scheduled_at). Chaque message que vous relisez porte scheduled_at, null pour un message envoyé immédiatement.

const { data, error } = await honkio.messages.send({
  from: '+1416XXXXXXX',
  to: '+1613XXXXXXX',
  body: 'Reminder: your appointment at Acme Dental is tomorrow at 2 pm.',
  scheduledAt: '2026-10-01T14:00:00-04:00',
  expiresAt: '2026-10-01T20:00:00-04:00', // optional: not after 8 pm (default: 24 hours after scheduledAt)
}, { idempotencyKey: 'appt-5521-reminder' })
if (error) throw new Error(`${error.name}: ${error.message}`)
console.log(data.id, data.status, data.scheduled_at, data.expires_at) // SCHEDULED

Un message programmé doit partir dans les 24 heures qui suivent son scheduled_at. Pour une limite plus serrée, par exemple un rappel qui ne sert plus à rien une fois le rendez-vous commencé, ajoutez expires_at : une date et heure ISO 8601 avec un décalage, d’au moins 1 minute et d’au plus 24 heures après scheduled_at, acceptée seulement avec scheduled_at (les clés de test suivent les mêmes règles ; toute autre valeur donne un 422 VALIDATION_ERROR). Un message qui ne peut pas partir avant sa limite échoue avec l’error_code SCHEDULED_SEND_EXPIRED : rien n’est envoyé, les frais sont remboursés et message.failed est déclenché. Chaque message que vous relisez porte expires_at, null quand aucun n’a été donné, ce qui veut dire le délai par défaut de 24 heures.

Tant qu’il est SCHEDULED, PATCH /v1/messages/:id avec un nouveau scheduled_at le déplace (la clé doit avoir messages:m) et DELETE /v1/messages/:id l’annule et rembourse les frais (messages:d). Une annulation est irréversible. L’un ou l’autre sur un message qui n’est plus programmé donne un 422 MESSAGE_NOT_SCHEDULED, avec details.current_status. Ils déclenchent message.rescheduled et message.cancelled.

// Move it, or cancel it (refunded), while it is still scheduled:
const moved = await honkio.messages.update('MESSAGE_ID', { scheduledAt: '2026-10-02T09:00:00-04:00' })
if (moved.error) throw new Error(moved.error.message)

const cancelled = await honkio.messages.cancel('MESSAGE_ID')
if (cancelled.error) throw new Error(cancelled.error.message)

PATCH accepte aussi expires_at, vérifié par rapport au nouveau scheduled_at, ou null pour le délai par défaut de 24 heures. Si vous l’omettez, un expires_at enregistré est gardé tant qu’il convient encore à la nouvelle heure (de 1 minute à 24 heures après) et abandonné sinon, ce qui ramène le délai par défaut de 24 heures. La réponse porte id, status, scheduled_at et expires_at.

Les vérifications d’envoi se font deux fois. Quand vous programmez, toutes s’appliquent sauf les limites d’envoi (limite quotidienne, rythme par numéro, plafonds par destinataire, destinataires distincts), et un compte en pause reçoit un 403 SENDING_PAUSED. À l’heure prévue, toutes s’appliquent de nouveau : si le destinataire s’est désabonné depuis, si le consentement est échu, si la clé a été révoquée, si le numéro ou le compte a été suspendu, ou si un loyer est dû, le message ne part pas. Il passe à FAILED avec ce error_code, est remboursé et déclenche message.failed.

Une limite d’envoi ou une pause ne fait pas échouer un message arrivé à son heure : il reste SCHEDULED et attend de pouvoir partir, puis il est remis à l’opérateur comme tout autre envoi. Si l’opérateur le ralentit alors (CARRIER_THROTTLED) ou ne peut pas être joint (CARRIER_UNAVAILABLE), le message repasse à SCHEDULED et est retenté : après l’attente demandée par l’opérateur (son Retry-After) quand il en indique une, sinon après environ 1, 2, 5 et 10 minutes, puis toutes les 10 minutes. Il n’est facturé qu’une fois, peu importe le nombre de tentatives. Un message envoyé immédiatement n’est pas retenté ainsi : il reçoit un 503 tout de suite. Aucune de ces attentes ne dépasse la limite : un message encore retenu par une pause, une limite d’envoi ou l’opérateur échoue alors avec SCHEDULED_SEND_EXPIRED et est remboursé, même si le compte reste en pause.

Un compte peut avoir à la fois 10 000 messages réels programmés et 1 000 messages de test programmés, un par message et un par membre d’une diffusion de groupe programmée. Au-delà, la programmation est refusée avec 429 SCHEDULED_LIMIT_REACHED (details contient mode, limit, scheduled et requested), et rien n’est facturé ; une diffusion qui ne tiendrait pas est refusée en entier. Un message cesse de compter dès qu’il est envoyé, échoue, expire ou est annulé.

Avec une clé de test, un message programmé est gratuit. À l’heure prévue, il est vérifié selon les consentements et les désabonnements de la clé de test, puis livré par simulation, jamais à un téléphone, et une pause ne le retient jamais. Une Idempotency-Key utilisée pour un envoi immédiat ne peut pas resservir pour un envoi programmé, ni l’inverse : 409 IDEMPOTENCY_KEY_REUSED.

GET /v1/messages accepte status=SCHEDULED pour lister ce qui attend, et status=CANCELLED pour ce qui a été annulé.