Enviar un correo
POST https://api.spacepost.dev/emails
Envía un correo a hasta 50 destinatarios to, con cc, bcc, archivos adjuntos, etiquetas, encabezados y una programación opcionales. La llamada responde en cuanto el correo se acepta y se pone en cola; la entrega ocurre de forma asíncrona y se notifica mediante webhooks.
Ejemplo
Sección titulada «Ejemplo»import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const { data, error } = await spacepost.emails.send({ from: "Acme <hello@acme.com>", to: ["user@example.com"], cc: ["manager@example.com"], bcc: ["archive@acme.com"], replyTo: "support@acme.com", subject: "Your invoice", html: "<p>Invoice attached.</p>", text: "Invoice attached.", headers: { "X-Entity-Ref-ID": "inv_1042" }, tags: [{ name: "category", value: "invoice" }],});
if (error) { console.error(error.statusCode, error.name, error.message);} else { console.log("Email id:", data.id);}curl -X POST https://api.spacepost.dev/emails \ -H "Authorization: Bearer $SPACEPOST_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "cc": ["manager@example.com"], "bcc": ["archive@acme.com"], "reply_to": "support@acme.com", "subject": "Your invoice", "html": "<p>Invoice attached.</p>", "text": "Invoice attached.", "headers": { "X-Entity-Ref-ID": "inv_1042" }, "tags": [{ "name": "category", "value": "invoice" }] }'import os, requests
r = requests.post( "https://api.spacepost.dev/emails", headers={"Authorization": f"Bearer {os.environ['SPACEPOST_API_KEY']}"}, json={ "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "cc": ["manager@example.com"], "bcc": ["archive@acme.com"], "reply_to": "support@acme.com", "subject": "Your invoice", "html": "<p>Invoice attached.</p>", "text": "Invoice attached.", "headers": {"X-Entity-Ref-ID": "inv_1042"}, "tags": [{"name": "category", "value": "invoice"}], },)print(r.json())payload := map[string]any{ "from": "Acme <hello@acme.com>", "to": []string{"user@example.com"}, "cc": []string{"manager@example.com"}, "reply_to": "support@acme.com", "subject": "Your invoice", "html": "<p>Invoice attached.</p>", "tags": []map[string]string{{"name": "category", "value": "invoice"}},}b, _ := json.Marshal(payload)req, _ := http.NewRequest("POST", "https://api.spacepost.dev/emails", bytes.NewReader(b))req.Header.Set("Authorization", "Bearer "+os.Getenv("SPACEPOST_API_KEY"))req.Header.Set("Content-Type", "application/json")res, err := http.DefaultClient.Do(req)$ch = curl_init('https://api.spacepost.dev/emails');curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SPACEPOST_API_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'from' => 'Acme <hello@acme.com>', 'to' => ['user@example.com'], 'reply_to' => 'support@acme.com', 'subject' => 'Your invoice', 'html' => '<p>Invoice attached.</p>', 'tags' => [['name' => 'category', 'value' => 'invoice']], ]),]);echo curl_exec($ch);require "net/http"require "json"
uri = URI("https://api.spacepost.dev/emails")req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer #{ENV["SPACEPOST_API_KEY"]}", "Content-Type" => "application/json")req.body = { from: "Acme <hello@acme.com>", to: ["user@example.com"], reply_to: "support@acme.com", subject: "Your invoice", html: "<p>Invoice attached.</p>", tags: [{ name: "category", value: "invoice" }]}.to_jsonres = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }puts res.body{ "id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10" }Cuerpo de la petición
Sección titulada «Cuerpo de la petición»fromstringobligatorioRemitente, como email@domain.com o Name <email@domain.com>. El dominio (o un dominio padre) debe estar verificado. La única excepción es onboarding@test.spacepost.dev, consulta Pruebas.
tostring | string[]obligatorioDestinatarios. Hasta 50 direcciones.
subjectstringobligatorioDe 1 a 998 caracteres.
htmlstringopcionalCuerpo en HTML. Se requiere al menos uno de html o text.
textstringopcionalCuerpo en texto plano. Si solo envías html, se genera una alternativa de texto por ti, lo que mejora la entregabilidad.
ccstring | string[]opcionalHasta 50 direcciones.
bccstring | string[]opcionalHasta 50 direcciones. Nunca se escriben en los encabezados del mensaje.
reply_tostring | string[]opcionalHasta 50 direcciones. También se acepta el alias en camelCase replyTo.
scheduled_atstringopcionalFecha ISO 8601 o lenguaje natural como in 1 hour, con un máximo de 30 días de antelación. Alias scheduledAt.
headersobjectopcionalEncabezados personalizados con claves y valores de tipo cadena.
attachmentsobject[]opcionalHasta 50 archivos adjuntos, cada uno con filename y exactamente uno de content o path.
tagsobject[]opcionalHasta 50 pares { name, value }. Nombres y valores: de 1 a 256 caracteres de A-Z a-z 0-9 _ -.
tenant_idstringopcionalModo plataforma: envía en nombre de un tenant (nuestro id o tu external_id). Opcional cuando el dominio de from pertenece a un tenant. Alias tenantId en el SDK de Node. Consulta Modo plataforma.
Más información: verificación de dominios, Pruebas, Programación de envíos, Encabezados personalizados, Archivos adjuntos, Etiquetas, Modo plataforma.
Las direcciones de cualquier campo pueden usar la forma Name <email@domain.com>.
Límites de un vistazo
Sección titulada «Límites de un vistazo»| Límite | Valor |
|---|---|
Destinatarios por campo (to, cc, bcc, reply_to) |
50 |
| Longitud del asunto | 998 caracteres |
| Archivos adjuntos | 50 por correo, filename de hasta 255 caracteres |
| Etiquetas | 50 por correo, nombre y valor de 1 a 256 caracteres de A-Z a-z 0-9 _ - |
| Tamaño total del mensaje | 40 MB, incluidos los archivos adjuntos y la sobrecarga de codificación |
| Horizonte de programación | 30 días |
| Correos por petición de lote | 100 (Envío por lotes) |
Respuesta
Sección titulada «Respuesta»Si todo va bien, devuelve 200 con el id del nuevo correo (un UUIDv7). Úsalo con GET /emails/{id} para leer el correo y su last_event.
Qué puede salir mal
Sección titulada «Qué puede salir mal»| Estado | name |
Significado |
|---|---|---|
| 401 | missing_api_key |
Falta el encabezado Authorization: Bearer. |
| 403 | invalid_api_key |
La clave es desconocida, fue revocada o no es una clave de SpacePost (sp_live_...). |
| 401 | restricted_api_key |
Se usó una clave restringida a un dominio con un dominio from distinto. |
| 403 | invalid_from_address |
El dominio de from no está verificado para tu equipo. |
| 403 | validation_error |
Se usa test.spacepost.dev como remitente con un destinatario que no es miembro del equipo ni una dirección de prueba. |
| 403 | sending_paused |
Tu equipo está pausado. |
| 422 | validation_error |
Falta un campo o no es válido. message indica el campo, por ejemplo `to`: Invalid email address. |
| 429 | daily_quota_exceeded |
Se alcanzó un límite diario: el del sandbox de cuenta nueva, tu tope según el nivel de confianza o el daily_limit de un tenant. |
| 429 | monthly_quota_exceeded |
Se alcanzó el límite mensual del plan Free. |
| 429 | rate_limit_exceeded |
Se superó tu límite de peticiones por segundo. Consulta Límites de tasa. |
Consulta la lista completa en Errores.
Después de la llamada
Sección titulada «Después de la llamada»El correo pasa por varios estados, expuestos como last_event: queued (o scheduled), sent y después delivered, delivery_delayed, bounced, complained, opened o clicked. Si todos los destinatarios están en tu lista de supresión, no se envía nada y el estado pasa a suppressed.