آموزش
ارسال ایمیل در Node.js
ارسال با Nodemailer (SMTP) یا fetch (API)، با مهلت و تلاش دوباره.
آخرین ویرایش: ۲۱ آبان ۱۴۰۵
دو راه دارید: SMTP با Nodemailer، یا API با fetch که در Node 18 به بعد هست. پیش از شروع، دامنهتان را تأیید و یک کلید بسازید و آن را در متغیر محیطی بگذارید:
SENDAK_USER=شناسه_کلید
SENDAK_KEY=sd_live_…با API
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) // queuedidempotencyKey را برای هر ایمیل منطقی ثابت بگیرید؛ نه برای هر تلاش. کد خطاها: خطاها و سقفها.
هشدار: فقط خطای
429و5xxرا دوباره امتحان کنید.4xxهای دیگر (مثل422) با تلاش دوباره درست نمیشوند.
با SMTP (Nodemailer)
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 را بردارید.
وبهوک
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)
})جزئیات رویدادها و تلاش دوباره: وبهوک.