# Send email your users can answer

One call sends from your domain or from a user's own Gmail or Outlook. Opt-outs and pacing are checked before it leaves, delivery comes back as webhooks, and a reply lands on the thread the send opened.

[Get started](/signup)
[Documentation](https://docs.aiinbx.com/guides/sending)

Example: one emails.send call from billing@mail.yourapp.com to ines@moreau.studio with an idempotency key returns at once with status queued, thread\_id thr\_5c1e, an empty suppressed list and no pacing hold. Later the webhook receives email.sent, then email.delivered when her mail server accepts it, then email.received when Ines replies asking for a VAT number — on the same thread, thr\_5c1e.

Mailboxes

## From your domain, or from your user's own inbox

The address in from decides what carries the mail. A verified domain sends it signed with your key; a connected Gmail or Outlook account sends it as that person, and it sits in their Sent folder. The call is the same.

[Mailboxes](/features/mailboxes)

Threading

## The answer comes back to the same thread

Every send opens a thread or joins one. When the recipient replies to a connected inbox, or to a domain whose MX record points here, it arrives as email.received with the thread\_id the send returned.

[Threading](/features/threading)

Checks

## Checked on the way out, told in the response

A suppressed recipient is dropped and a held message waits in a queue. Unless every address in to is suppressed, neither is an error: the response says what happened, so there is no list to check first and nothing to retry.

-   [Suppressions](/features/suppressions)

    Addresses that bounced, complained or opted out are dropped and listed in suppressed. Mark a newsletter unsubscribe: true and an opt-out stops that list, not the receipts you send under another key.

-   [Pacing](/features/pacing)

    Caps per sender, domain and recipient hold a message in a queue rather than refusing it. pacing names the rule and when it should leave.

-   [Sending hours](/features/sending-hours)

    Mail sent outside the hours you allow waits for the window to open. The password reset passes pacing: { skip: true } and goes at once.

Example: a usage report sent to ines@moreau.studio with marc@moreau.studio in cc. Marc's address hard-bounced last month, so the response lists him in suppressed and the report goes to Ines alone. It was sent on a Sunday night, so pacing reports it held by a sending-hours rule until Monday 07:00 UTC.

Webhooks

## Delivery arrives as events, not a status to poll

What happens after the handoff is posted to your endpoint, signed and retried for a day if it is down. Every delivery event carries the email and thread id, so a bounce finds its customer.

[Webhooks](https://docs.aiinbx.com/webhooks/events)

Reference

## Everything one send takes

Only from, to, subject and a body are required. The rest is there for the day you need it, on the same endpoint, and a reply takes the same options.

[Sending guide](https://docs.aiinbx.com/guides/sending)

-   from: An address on a verified domain or a connected mailbox, with a display name if you want one.
-   to · cc · bcc · reply\_to: One address or a list, up to 100 in each.
-   html · text: One or both, up to 2 million characters each. The TypeScript SDK also takes a React Email component as react.
-   attachments: Up to 20 files in base64, 3 MB in all. Give one a cid to show it inline.
-   headers: Your own headers, such as a campaign or correlation id. Message-ID and the threading headers are written for you.
-   Idempotency-Key: A request header. A retry with the same key and body returns the first email instead of sending a second.
-   suppression\_key · unsubscribe: Which opt-out list the send answers to, and whether the mail is optional. Optional mail goes to one recipient per send.
-   pacing · tracking: Skip the rules for urgent mail; turn open and click tracking on or off for one email from a domain.
-   scheduled\_at: Send later, up to 30 days out. It can be moved or cancelled until its time comes.
-   thread\_id · in\_reply\_to: Continue a conversation, including one that began before it ever passed through AI Inbx.

Set up

## Sending, in three calls

The same calls in the TypeScript and Python SDKs, or as plain HTTPS with a bearer key from any language.

Step 1, domains.create: or start on your-slug.aiinbx.app.

```
const domain = await aiinbx.domains.create({
  name: "mail.yourapp.com",
})

domain.records // DKIM, SPF, DMARC, return path, and MX for replies
```

Step 2, emails.send: a key per invoice, so a retry never sends twice.

```
await aiinbx.emails.send(
  {
    from: "Yourapp Billing <billing@mail.yourapp.com>",
    to: invoice.customerEmail,
    subject: "Invoice INV-2041",
    react: <InvoiceEmail invoice={invoice} />,
    text: invoiceText(invoice),
  },
  { idempotencyKey: "invoice-2041" },
)
```

Step 3, your webhook: bounces recorded, answers opened.

```
const event = await verifyWebhookRequest(request, secret)

if (event.type === "email.bounced" && event.data.permanent) {
  await customers.markUndeliverable(event.data.recipients)
}
if (event.type === "email.received") {
  await billing.openQuery(event.data.thread_id)
}
```

## Questions

### What is a transactional email API?

An HTTP endpoint your application calls to send one message to one person because of something they did: a receipt, a password reset, an invoice, a notification. The service signs the mail for your domain, hands it to the receiving server and reports back what happened, so your app never talks SMTP or runs a mail server.

### Should I send email over SMTP or an HTTP API?

AI Inbx is an HTTP API only; there is no SMTP relay. A send is one HTTPS request, which works from serverless and edge runtimes where outbound SMTP is often blocked, and it answers at once with the email's id, its thread and whether anything held or dropped it, where an SMTP relay only says it took the message.

### How do I keep transactional email out of the spam folder?

Send from a domain you authenticate. Adding a domain returns its DKIM, SPF, DMARC and return-path records, and it cannot send until DKIM verifies; diagnostics reads the live zone when a record will not resolve. Suppressions then stop you mailing addresses that bounced or complained, which is what mailbox providers judge a sender on.

### Can my app send email from my users' own Gmail or Outlook address?

Yes. The user connects the account once over OAuth, from a hosted page, a link or an API call, and their address works as a from like any domain address. The mail goes out through Google or Microsoft, sits in their own Sent folder, and replies come back on the same thread. Mail sent this way is not open or click tracked.

### Can I send from my customers' domains?

Yes. The domain does not have to be yours: add it, show your customer the records to publish, and wait for domain.verified. To skip their DNS entirely, verify one wildcard such as \*.yourapp.com and give each customer a subdomain like acme.yourapp.com, which sends straight away.

### How do I know whether an email was delivered?

Subscribe to the delivery webhooks. email.delivered fires when the receiving server accepts the message, naming the recipients it covers; email.bounced, email.complained and email.failed say when it did not. Acceptance by the receiving server is as far as any email API can see, so no service can promise inbox rather than spam folder placement.

### Can I still email someone who unsubscribed?

Transactional mail, yes. The unsubscribe link and the mail client's button put the address on the list named by the send's suppression\_key, where it only blocks mail marked unsubscribe: true, so a receipt or password reset still arrives. Someone who chooses to stop everything, or asks in a reply, blocks all mail on that list, as a spam complaint does; a hard bounce blocks every send to that address.

### How do I stop a retry from sending the same email twice?

Pass an Idempotency-Key header derived from your own data, such as invoice-2041. A retry with the same key and body returns the original email with Idempotent-Replayed: true instead of sending again, and the same key with a different body fails with idempotency\_key\_reused.

Keep reading

## More on building with email

-   [Scheduled email APISchedule an email up to 30 days ahead, then move or cancel it by id until its time comes. It is composed then, so suppressions and threads stay current.ThreadingSending hoursSuppressions](/solutions/scheduled-email-api)
-   [SPF DKIM DMARC checkCheck a customer's SPF, DKIM and DMARC records over an API. Live lookups at their own nameservers name the record that is wrong and the change that fixes it.Wildcard domains](/solutions/spf-dkim-dmarc-check)
-   [Email open trackingTrack email opens and clicks per domain or per send. Apple privacy prefetches and security scanner clicks arrive flagged as bots, each with its reason.Threading](/solutions/email-open-tracking)

-   [ThreadingOne conversation, however it comes back.](/features/threading)
-   [MailboxesGmail and Outlook, same everything.](/features/mailboxes)
-   [PacingLimits per domain, mailbox and recipient.](/features/pacing)
-   [SuppressionsUnsubscribes honoured on every send.](/features/suppressions)

[All solutions](/solutions)

## Give your product an inbox. The hard parts are already handled.

[Get started](/signup)
