Node.js, Python, cURL로 트랜잭션 이메일 보내는 방법

Node.js, Python, PHP, 커맨드라인에서 REST API로 이메일을 보내는 복사해서 바로 쓰는 예제. 타임아웃, 오류 처리, 재사용 가능한 헬퍼 함수까지 담았습니다.

트랜잭션 이메일은 사용자가 어떤 행동을 했기 때문에 제품이 보내는 메시지입니다. 회원가입, 비밀번호 재설정, 인보이스 결제, 워크스페이스 초대 같은 경우입니다. 이런 메일은 빠르게, 여러분의 도메인에서, 그리고 발송을 일으킨 요청을 느리게 만들지 않고 나가야 합니다.

이 글에서는 팀들이 가장 많이 쓰는 언어로 Inbixio REST API를 호출해 이메일을 보내는 방법을 보여 주고, 일회성 호출을 어떤 코드베이스에든 넣을 수 있는 작은 헬퍼 함수로 바꿔 봅니다.

요청 정리

아래의 모든 예제는 같은 HTTP 요청을 보냅니다.

  • 메서드와 URL: POST https://backend.inbixio.com/api/send-email/(끝의 슬래시 포함)
  • 인증: Send(발송) 권한이 있는 키를 담은 X-API-Key 헤더
  • 본문: to_email, subject, message(HTML)를 담은 JSON

호출에 성공하면 200과 함께 {"message": "Email queued successfully."}를 반환합니다. 아직 키를 만들지 않았거나 SMTP 서버를 연결하지 않았다면 5분 빠른 시작 가이드부터 시작하세요.

모든 예제에서 키는 INBIXIO_API_KEY라는 환경 변수에서 읽습니다. 키는 서버에 두고, 브라우저나 모바일 앱에는 절대 포함하지 마세요.

cURL

키를 테스트하거나, 서버에서 디버깅하거나, 셸 스크립트나 cron 작업에서 호출할 때 유용합니다.

curl -sS -X POST https://backend.inbixio.com/api/send-email/ \
  -H "X-API-Key: $INBIXIO_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 15 \
  -d '{
    "to_email": "ada@example.com",
    "subject": "Your invoice is ready",
    "message": "<p>Hi Ada, your invoice for October is ready.</p>"
  }'

-sS는 진행률 표시줄을 숨기면서도 오류는 출력하고, --max-time은 네트워크가 멈췄을 때 스크립트가 무한정 대기하지 않게 합니다.

Node.js

Node.js 18 이상에는 fetch가 내장되어 있으므로 HTTP 라이브러리가 필요 없습니다.

// send-email.js
const INBIXIO_URL = 'https://backend.inbixio.com/api/send-email/';

export async function sendEmail({ to, subject, html }) {
  const res = await fetch(INBIXIO_URL, {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.INBIXIO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to_email: to, subject, message: html }),
    signal: AbortSignal.timeout(15_000),
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    const reason = body.error || body.detail || res.statusText;
    throw new Error(`Inbixio ${res.status}: ${reason}`);
  }
  return body; // { message: 'Email queued successfully.' }
}

Express 라우트에서 사용하는 예:

app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await sendEmail({
    to: user.email,
    subject: 'Welcome aboard',
    html: `<h1>Welcome, ${escapeHtml(user.name)}!</h1><p>Your account is ready.</p>`,
  });
  res.status(201).json({ id: user.id });
});

escapeHtml에 주목하세요. message 필드는 HTML로 렌더링되므로, 사용자의 이름처럼 사용자가 입력한 값은 본문에 넣기 전에 반드시 이스케이프해야 합니다.

Python

requests를 사용하는 경우:

# send_email.py
import os
import requests

INBIXIO_URL = "https://backend.inbixio.com/api/send-email/"

class EmailError(Exception):
    pass

def send_email(to: str, subject: str, html: str) -> dict:
    res = requests.post(
        INBIXIO_URL,
        headers={"X-API-Key": os.environ["INBIXIO_API_KEY"]},
        json={"to_email": to, "subject": subject, "message": html},
        timeout=15,
    )
    if not res.ok:
        try:
            body = res.json()
        except ValueError:
            body = {}
        reason = body.get("error") or body.get("detail") or res.reason
        raise EmailError(f"Inbixio {res.status_code}: {reason}")
    return res.json()

json=을 전달하면 Content-Type 헤더가 자동으로 설정됩니다. timeout은 항상 지정하세요. requests는 기본적으로 무한정 대기합니다.

Django에서는 데이터베이스 트랜잭션이 커밋된 뒤에 호출해, 롤백된 행에 대한 이메일이 발송되는 일이 없도록 하세요.

from django.db import transaction

transaction.on_commit(lambda: send_email(user.email, "Welcome!", render_welcome(user)))

PHP

<?php
function send_email(string $to, string $subject, string $html): array {
    $ch = curl_init('https://backend.inbixio.com/api/send-email/');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_HTTPHEADER => [
            'X-API-Key: ' . getenv('INBIXIO_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'to_email' => $to,
            'subject'  => $subject,
            'message'  => $html,
        ]),
    ]);
    $body = json_decode(curl_exec($ch), true) ?? [];
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($status !== 200) {
        throw new RuntimeException("Inbixio $status: " . ($body['error'] ?? $body['detail'] ?? 'request failed'));
    }
    return $body;
}

여러 명에게 보내기

요청 하나는 하나의 to_email로 전달됩니다. 여러 사용자에게 알리려면 수신자마다 요청을 하나씩 보내세요. 어차피 트랜잭션 이메일에서는 이 방식이 바람직합니다. 모든 사용자가 자신만의 메시지를 받고, 누구도 다른 사람의 주소를 볼 수 없으며, 잘못된 주소 하나가 나머지 발송을 막지 않습니다.

자체 SMTP 서버에 과부하가 걸리지 않도록 동시 요청 수는 적당히 유지하세요. 대부분의 앱에는 5–10개의 병렬 요청이면 충분합니다.

요청 경로에서 직접 보내지 않기

API 호출은 빠르지만, 그래도 네트워크 왕복이 발생합니다. 시간이 중요하지 않은 발송은 백그라운드 작업(BullMQ, Celery, Sidekiq, 클라우드 큐)으로 넘기세요. 그러면:

  • 사용자의 페이지가 이메일을 기다리지 않고 로드되고,
  • 일시적인 오류를 사용자가 눈치채지 못하게 재시도할 수 있으며,
  • 수천 통의 환영 이메일을 일으키는 대량 가져오기 같은 갑작스러운 급증도 일정한 속도로 처리됩니다.

OTP 코드와 비밀번호 재설정은 사용자가 실시간으로 기다리고 있으므로 즉시 보내고, 실패하면 친절한 오류 메시지를 보여 주세요. 어떤 오류를 재시도하고 어떤 오류를 사용자에게 보여 줄지는 이메일 API 오류와 한도 처리하기에서 설명합니다.

배포 전 체크리스트

  • API 키는 환경 변수나 시크릿 매니저에서 가져오며, 키에는 Send 권한만 있습니다.
  • HTML에 들어가는 사용자 입력 값은 모두 이스케이프합니다.
  • 요청에는 10–15초의 타임아웃이 있습니다.
  • 200이 아닌 응답은 응답 본문과 함께 로그로 남겨 error와 detail 메시지를 잃지 않습니다.
  • 발신 도메인에 SPF, DKIM, DMARC가 설정되어 있어 이메일이 받은편지함에 도착합니다.

Inbixio로 보내세요

자체 SMTP 서버를 연결하고, 권한이 제한된 API 키를 만들어 HTTPS 요청 한 번으로 어떤 언어에서든 발송하세요.

계정 만들기 요금제 보기