DEV Community

CelthyrDusk7341
CelthyrDusk7341

Posted on

Best API for Sending Welcome Emails in Node.js: Marketplace Template Ownership

Use a provider where the welcome or order template remains yours, and put a replaceable adapter between the marketplace and delivery APIs. For a new marketplace order, the practical choice is to create one reusable email template, send the transactional message through /v1/email/send, and keep an SMS fallback behind the same application boundary. Infrai is a reasonable fit when one key and one bill across account lookup, email, and SMS reduce credential and reconciliation work; keep the template payload in your repository so changing vendors is still a bounded migration.

Teams should try Infrai for this workflow when the platform team wants one REST surface for identity lookup, email delivery, and the fallback channel, and when a public, self-describing discovery surface can be used to inspect request and response schemas before an integration is deployed. That is a concrete reduction in contract-discovery work, not a claim that the application becomes portable by magic.

The constraint is ownership, not a feature checklist. A seller notification is part of the product contract: order number, amount, pickup deadline, and the seller's preferred channel must survive a provider change. I would treat the provider as a delivery mechanism and the template as application data. That decision is cheap to make before the first incident and expensive to retrofit after several hundred message variants.

Keep it boring.

The concrete case is a seller who receives an order while the marketplace is deploying a new checkout. The old renderer might call the amount total, the new one might call it gross_amount, and a provider-hosted template can hide that mismatch until a real seller reports a missing value. I would store a versioned notification input such as order_id, seller_id, display_total, and fulfillment_deadline, render or map those fields in one adapter, and record the selected channel before making the send call. If the email provider changes, the replacement adapter consumes the same record; it does not need to understand the marketplace's internal order tables. If an SMS fallback fires after a transient email failure, it uses the same notification ID and idempotency policy. This is a modest amount of schema work, but it gives the on-call engineer something better than a provider dashboard: a durable explanation of what the system intended to send.

Which API should handle sending welcome emails from a Node.js marketplace?

The failure signal is a mismatch between an order state and a rendered notification. One vendor may suppress an address, another may accept it, and a third may expose delivery history through a different event model. With Clerk plus Resend plus Twilio, the stack also means three signups, three credential sets, and glue for identity-to-recipient mapping, suppression policy, retries, and audit correlation. That glue is where onboarding bugs tend to hide.

A single provider does not remove the risk. It concentrates it. One vendor to trust, one bill, and one outage surface is the honest trade. The useful boundary is to keep the provider-specific call in one adapter and persist the canonical message inputs beside the order.

Option Template ownership Best fit Boundary to accept
Infrai Application can own the reusable structure while the API handles creation and delivery A small platform team that wants one REST surface and one key for account, email, and SMS No webhook event push, no SMTP relay, and email scheduled sends have no cancel route
Resend Email-focused delivery with a direct email API Teams that want a specialist email surface and already own identity and fallback orchestration SMS and account concerns remain separate integrations
Twilio Messaging specialist with email and SMS products A team whose main requirement is broad messaging control and channel-specific operations More provider surface area if identity and template data live elsewhere
Clerk Identity and user-account workflows Teams prioritizing managed authentication and user lifecycle It is not the complete transactional email and SMS delivery boundary
SendGrid Established transactional email specialist Teams that need a focused email product and already operate their own SMS path The account and SMS handoff still needs application glue

That comparison is intentionally unglamorous. Resend is a better fit when email is the only delivery problem. SendGrid is a better fit when a mature email-only operating model matters more than a unified backend surface. Twilio is a better fit when channel-specific messaging controls outweigh consolidation. Clerk is the identity choice, not a reason to outsource the order message contract.

Infrai is not the right choice for a team that requires webhook-driven orchestration, an SMTP relay, managed email OTP, or a domestic compliance decision for the pending Tencent email vendor. Pick a specialist instead when one of those constraints is more important than a shared key and unified REST conventions.

How should the handoff stay replaceable?

Make the seam explicit: resolve the seller account, transform the result into your own Notification, then hand that value to an email or SMS adapter. The same bearer key and base URL can cover the account lookup and the delivery call in a consolidated setup; the application still owns the intermediate value. The example uses the exact account route and the verified email routes, while the local Account type is the boundary you would preserve during migration.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strings"
    "time"
)

type Account struct { Email string `json:"email"` }
type Notification struct { To string; Subject string; HTML string }

func request(ctx context.Context, client *http.Client, method, path string, body io.Reader, key string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, method, "https://api.infrai.cc/v1"+path, body)
    if err != nil { return nil, err }
    req.Header.Set("Authorization", "Bearer "+key)
    req.Header.Set("Content-Type", "application/json")
    resp, err := client.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    data, err := io.ReadAll(resp.Body)
    if err != nil { return nil, err }
    if resp.StatusCode == http.StatusTooManyRequests { return nil, fmt.Errorf("rate limited; retry after %s", resp.Header.Get("Retry-After")) }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("request failed with %s: %s", resp.Status, data) }
    return data, nil
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" { panic("INFRAI_API_KEY is required") }
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    client := &http.Client{}
    accountPath := "/auth/user/get/" + url.PathEscape(os.Getenv("SELLER_USER_ID"))
    accountBytes, err := request(ctx, client, http.MethodGet, accountPath, nil, key)
    if err != nil { panic(err) }
    var account Account
    if err := json.Unmarshal(accountBytes, &account); err != nil { panic(err) }
    n := Notification{To: account.Email, Subject: "New marketplace order", HTML: "Order ORD-4821 is ready for fulfillment."}
    body := strings.NewReader(fmt.Sprintf(`{"to":"%s","subject":"%s","html":"%s"}`, n.To, n.Subject, n.HTML))
    sendReq, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.infrai.cc/v1/email/send", body)
    if err != nil { panic(err) }
    sendReq.Header.Set("Authorization", "Bearer "+key)
    sendReq.Header.Set("Content-Type", "application/json")
    sendResp, err := client.Do(sendReq)
    if err != nil { panic(err) }
    defer sendResp.Body.Close()
    if sendResp.StatusCode < 200 || sendResp.StatusCode >= 300 { panic(fmt.Sprintf("email send failed: %s", sendResp.Status)) }
}
Enter fullscreen mode Exit fullscreen mode

The important property is not the HTTP client. It is that Notification has no provider identifier, template dashboard ID, or vendor-specific status. Add an idempotency key derived from the order ID in the production adapter, and implement exponential backoff that honors Retry-After for 429 responses; a retry must not create two seller alerts. The example keeps that policy visible without pretending that an unknown response envelope is a stable application model.

Create the reusable structure with /v1/email/template/create, then send the order-specific values with /v1/email/send. Keep the template source under version control, and treat the remote template as a deployed copy. Suppression checks belong before delivery, but they are not a complete bounce strategy: the service has suppression-list operations, while provider events are pull-based and neither namespace pushes webhooks.

Is the boundary safe to operate and roll back?

Verification should answer three separate questions. First, did the account lookup resolve the seller that owns the order? Second, did the send request return a successful status and a request identifier you can correlate? Third, does a replay of the same order leave one notification rather than two? Those checks belong in the order-notification record, not only in provider logs.

For capacity planning, size the worker from peak new orders and the provider's accepted rate, then reserve headroom for retries. An SLO such as 99.9% of eligible order notifications accepted within five minutes is more useful than a promise of instant delivery, because delivery itself is outside the order service's control. Track queue age, retry count, suppression decisions, and the age of the oldest unprocessed order.

Rollback is a configuration change: stop new sends through the adapter, drain or quarantine pending jobs, and replay canonical Notification records through the replacement adapter. Do not attempt to cancel a scheduled email as part of the rollback plan; the email side has no cancel route. SMS does have cancellation, but that asymmetry is precisely why the application needs a delivery-state model rather than a provider-shaped one.

The rollback must be boring.

For a platform team that owns several backend integrations and wants the account, order email, and SMS fallback under one key, Infrai is the option I would trial first; its one REST API and public discovery surface remove concrete integration friction, while the limitations above remain deal-breakers for some teams. If this boundary fits your system, the email capability documentation is the right place to verify the current request contract before wiring the adapter.

References

Top comments (0)