رفتن به محتوای اصلی
سندک

آموزش

ارسال ایمیل در Node.js

ارسال با Nodemailer (SMTP) یا fetch (API)، با مهلت و تلاش دوباره.

آخرین ویرایش: ۲۱ آبان ۱۴۰۵

دو راه دارید: SMTP با Nodemailer، یا API با fetch که در Node 18 به بعد هست. پیش از شروع، دامنه‌تان را تأیید و یک کلید بسازید و آن را در متغیر محیطی بگذارید:

.env
SENDAK_USER=شناسه_کلید
SENDAK_KEY=sd_live_…

با API

send.mjs
const API = 'https://api.sendak.ir/v1/emails'

export async function sendEmail({ idempotencyKey, to, subject, text, html, tags }) {
  for (let attempt = 0; attempt < 4; attempt++) {
    let response

    try {
      response = await fetch(API, {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.SENDAK_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': idempotencyKey,
        },
        body: JSON.stringify({ from: 'hello@shop.ir', to, subject, text, html, tags }),
        signal: AbortSignal.timeout(10_000),
      })
    } catch {
      // Network error or timeout: the key makes a second try safe.
      await sleep(2 ** attempt * 1000)
      continue
    }

    if (response.ok) return (await response.json()).data
    if (response.status !== 429 && response.status < 500) {
      throw new Error(`Sendak refused the message: ${await response.text()}`)
    }

    await sleep((Number(response.headers.get('Retry-After')) || 2 ** attempt) * 1000)
  }

  throw new Error('Sendak did not accept the message after 4 tries')
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
استفاده
import { sendEmail } from './send.mjs'

const [result] = await sendEmail({
  idempotencyKey: `receipt-${order.id}`,
  to: ['ali@gmail.com'],
  subject: `رسید سفارش ${order.number}`,
  text: 'سفارش شما ثبت شد.',
})

console.log(result.id, result.status) // queued

idempotencyKey را برای هر ایمیل منطقی ثابت بگیرید؛ نه برای هر تلاش. کد خطاها: خطاها و سقف‌ها.

هشدار: فقط خطای 429 و 5xx را دوباره امتحان کنید. 4xxهای دیگر (مثل 422) با تلاش دوباره درست نمی‌شوند.

با SMTP (Nodemailer)

smtp.mjs
import nodemailer from 'nodemailer'

const transport = nodemailer.createTransport({
  host: 'smtp.sendak.ir',
  port: 587,
  secure: false, // STARTTLS: false means "start plain, then upgrade"
  requireTLS: true,
  auth: { user: process.env.SENDAK_USER, pass: process.env.SENDAK_KEY },
  pool: true,
  connectionTimeout: 10_000,
  socketTimeout: 20_000,
})

await transport.sendMail({
  from: 'hello@shop.ir',
  to: 'ali@gmail.com',
  subject: 'اولین ایمیل',
  text: 'سلام از سندک',
})
  • secure: false با requireTLS: true یعنی اتصال با STARTTLS رمزنگاری می‌شود. secure: true یعنی پورت ۴۶۵ که پشتیبانی نمی‌شود.
  • pool: true اتصال را نگه می‌دارد و برای هر ایمیل اتصال تازه نمی‌سازد.
  • مهلت‌ها را کوتاه و مشخص بگذارید تا درخواست کاربر گیر نکند.

نکته: SMTP کلید یکتایی درخواست ندارد. برای ایمیل‌هایی که ارسال تکراری‌شان ضرر دارد، API را بردارید.

وب‌هوک

webhook.mjs (Express)
import crypto from 'node:crypto'
import express from 'express'

const app = express()

// Sign the RAW body: do not parse it with express.json() before checking.
app.post('/webhooks/sendak', express.raw({ type: 'application/json' }), (req, res) => {
  const parts = Object.fromEntries(String(req.get('X-Sendak-Signature')).split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  const expected = crypto.createHmac('sha256', process.env.SENDAK_WEBHOOK_SECRET).update(`${t}.${req.body}`).digest('hex')
  const ok = Math.abs(Date.now() / 1000 - t) <= 300 && parts.v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))

  if (!ok) return res.sendStatus(401)

  const event = JSON.parse(req.body)
  // Put the event on your own queue and answer fast.
  res.sendStatus(200)
})

جزئیات رویدادها و تلاش دوباره: وب‌هوک.

کلید ندارید؟

با پلن رایگان، ۱٬۰۰۰ ایمیل در ماه می‌فرستید.