Reply to inbound email with Scaleway Transactional Email
At the end, email to your support address reaches your app through EmailConnect, your replies go out through Scaleway Transactional Email, and both sides of the conversation stay in one thread in your customer's mail client.
By Xander Groesbeek, founder · Published November 2026 · Updated November 2026 · 7 min read
In brief
What you'll build. A two-way support address: customer emails arrive in your app as JSON, and your replies go out from your own domain, threaded with the original message.
What you need. An EmailConnect account (the threading fields are on every plan, Free included), a Scaleway account with Transactional Email (TEM), and a domain whose DNS you can edit. If that domain's DNS zone is at Scaleway Domains and DNS, TEM can create its sending records for you.
How it works. EmailConnect receives and parses; TEM sends. Your app sits in between: it stores the Message-IDs of each conversation and sets the threading headers on every reply it sends.
The steps at a glance
- 1
Choose your domain layout
Where mail arrives, where replies come from, and which MX records stay put.
- 2
Set up the inbound side
An EmailConnect alias that posts each email to your endpoint.
- 3
Authenticate the sending domain in TEM
SPF, DKIM and DMARC, without touching your MX.
- 4
Store each conversation as mail arrives
Keep the Message-IDs from the payload.
- 5
Send the reply through TEM
With the threading headers and a tagged reply address.
- 6
Match the customer's next reply
By tag first, by Message-ID as a fallback.
- 7
Track bounces on your replies
TEM webhooks, delivered through a Scaleway topic.
EmailConnect only receives email; it does not send it on your behalf, and we don't plan to. Sending well is a different job (IP reputation, bounces, complaints), and dedicated senders do it better. Scaleway TEM is a good match: a French provider that sends e-mail out reliably.
Step 1: Choose your domain layout
This is the step that decides whether replies come back at all. Each domain has one set of MX records, and they say where mail to that domain goes. Sending through TEM doesn't need them changed, but TEM's setup screens suggest an MX record, and following that suggestion on the wrong domain is how replies get lost.
| Layout | Replies sent from | MX records | Good for |
|---|---|---|---|
| A. Main domain | support@yourdomain.com | Main domain: unchanged (your mailbox provider). in.yourdomain.com: EmailConnect | Most setups: customers see your normal address |
| B. One subdomain for both | support@in.yourdomain.com | in.yourdomain.com: EmailConnect | App-generated mail, platforms sending for their own customers |
In both layouts, replies from your customers come back to EmailConnect: in A because the reply address you set (step 5) is on in.yourdomain.com, in B because that is the sending domain itself. The subdomain strategy explains why a subdomain keeps your main mail untouched.
The blackhole MX
TEM recommends an MX record on your sending domain and, if you have no mail server, suggests Scaleway's blackhole MX (blackhole.tem.scaleway.com). Scaleway is clear that mail sent to it is lost for good. Your sending domain already has an MX: your mailbox provider (layout A) or EmailConnect (layout B). Keep it, and never add the blackhole record to a domain whose replies you want.
Step 2: Set up the inbound side
Add in.yourdomain.com as a domain in EmailConnect, point its MX record at EmailConnect, and create a support@in.yourdomain.com alias with your endpoint as its webhook. The domain setup guide has the exact records. In layout A, forward support@yourdomain.com to the alias in your mailbox provider, so new conversations arrive too.
Turn on webhook signing and check the signature before your app acts on an email: your endpoint is about to start sending mail in response to what it receives.
Step 3: Authenticate the sending domain in TEM
In the Scaleway console, add the domain you send from (yourdomain.com in layout A, in.yourdomain.com in B) to Transactional Email, then add the records it shows you:
- SPF. A domain can have only one SPF record. If it already has one (your mailbox provider's), add
include:_spf.tem.scaleway.comto it rather than creating a second record. Mind the SPF limit of ten DNS lookups per check. - DKIM. The TXT record TEM generates for your domain.
- DMARC. If the domain has no DMARC record yet, start with
p=noneand a report address. Already have one? Leave it; TEM's SPF and DKIM make your replies align with it. - MX. Leave it as it is (see step 1).
Is the domain's DNS zone at Scaleway Domains and DNS? Then you can switch on autoconfiguration for the TEM domain, and Scaleway writes the SPF, DKIM and DMARC records for you and keeps them up to date. It writes an MX record too, so look at the zone afterwards: the MX records must still point at your mailbox provider (layout A) or at EmailConnect (layout B), with no blackhole record next to them.
Wait until TEM shows the domain as checked before you send real replies.
Step 4: Store each conversation as mail arrives
Threading in mail clients runs on Message-IDs. Every webhook payload carries three of them, on every plan and whether or not the alias includes envelope data:
Part of the webhook payload (see the payload reference)
{
"id": "msg_…",
"message": {
"sender": { "email": "customer@example.com", "name": "Sarah Johnson" },
"recipient": { "email": "support+c_4821@in.yourdomain.com", "tag": "c_4821" },
"subject": "Can't reset my password"
},
"envelope": {
"messageId": "<CAB1x7@mail.example.com>",
"inReplyTo": null,
"references": []
}
} Store a conversation per thread, and every messageId that belongs to it: the ones you receive and the ones you send. EmailConnect keeps an email only for your retention window, so this history has to live in your app. Deduplicate on the top-level id (or the webhook-id header), not on messageId: the sender chooses that one.
Step 5: Send the reply through TEM
A threaded reply needs three things: In-Reply-To set to the message you answer, References set to the conversation so far plus that message, and a subject starting with Re:. Add a Reply-To with a plus tag that names the conversation, so the customer's next reply tells you directly which thread it belongs to.
Sending a reply with the TEM REST API (TypeScript)
// inbound = the stored payload you are answering
// conversationId = your own id for the thread, e.g. "c_4821"
const references = [...inbound.envelope.references, inbound.envelope.messageId]
const subject = /^re:/i.test(inbound.message.subject ?? '')
? inbound.message.subject
: `Re: ${inbound.message.subject ?? ''}`
const res = await fetch(
'https://api.scaleway.com/transactional-email/v1alpha1/regions/fr-par/emails',
{
method: 'POST',
headers: {
'X-Auth-Token': process.env.SCW_SECRET_KEY!,
'Content-Type': 'application/json'
},
body: JSON.stringify({
project_id: process.env.SCW_PROJECT_ID,
from: { email: 'support@yourdomain.com', name: 'Acme Support' },
to: [{ email: inbound.message.sender.email, name: inbound.message.sender.name ?? undefined }],
subject,
text: replyText,
additional_headers: [
{ key: 'In-Reply-To', value: inbound.envelope.messageId },
{ key: 'References', value: references.join(' ') },
{ key: 'Reply-To', value: `support+${conversationId}@in.yourdomain.com` }
]
})
}
)
const { emails } = await res.json()
// Store with the conversation: emails[0].id (bounce events refer to it, step 7)
// and emails[0].message_id (see the note below) Before you go live, send a test reply to a Gmail or Outlook address, open the original message and check that In-Reply-To, References and Reply-To arrived as you set them, and that the reply sits in the same thread. TEM's API accepts custom headers as key-value pairs, but its documentation doesn't list which ones it passes through unchanged. If one goes missing, send through TEM's SMTP relay instead, where you build the whole message, headers included, with your usual mail library.
TEM returns a message_id for each email it accepts. Store it with the conversation, and check in your test that it matches the Message-ID header the recipient sees. If it doesn't, the tag in Reply-To still links the customer's next reply; the Message-ID only serves as a fallback.
Step 6: Match the customer's next reply
When the customer answers, the reply arrives at your alias with the tag and the threading fields filled in:
Finding the conversation (TypeScript)
async function findConversation(payload) {
// 1. The tag from your Reply-To address
const tag = payload.message.recipient.tag
if (tag) {
const conv = await db.conversations.findById(tag)
// Tags are free-form: check it exists and belongs to this sender
if (conv && conv.customerEmail === payload.message.sender.email) return conv
}
// 2. Fallback: any Message-ID you have seen in a thread
const ids = [payload.envelope.inReplyTo, ...payload.envelope.references].filter(Boolean)
return db.conversations.findByMessageId(ids) // null means: a new conversation
} Both signals come from the sender's side, so treat them as a way to link messages, not as proof of who sent them. A forwarded email can carry an old In-Reply-To; a customer can edit the address they reply to. For anything sensitive, check message.sender.email and, on Maker and above, the SPF/DKIM/DMARC results in spam.authentication.
Step 7: Track bounces on your replies
A reply that bounces is a customer who thinks you never answered. TEM can report what happens to every email you send: delivered, deferred (a soft bounce it retries), dropped (a hard bounce), mailbox not found, marked as spam, or blocked by your blocklist. These events don't come to a URL directly: TEM pushes them to a topic in Scaleway's Topics and Events service, and you subscribe your endpoint to that topic.
- Create a topic in Topics and Events, and subscribe an HTTPS endpoint of your app to it.
- In TEM, create a webhook for your sending domain, pick the event types you want, and select that topic.
- In your endpoint, match each event to your reply by its email ID, and flag the conversation when a reply bounced.
See Scaleway's Transactional Email documentation for the webhook setup and the event format. We use these webhooks ourselves for the mail EmailConnect sends.
Limits worth knowing
| Limit | What it means for replies |
|---|---|
| Transactional only | TEM is for one-to-one mail such as support replies; newsletters and marketing are not allowed. |
| Subject length | Scaleway's API guide asks for subjects of at least 10 characters, so a reply to "Hi" needs a longer subject. |
| Attachments | Up to 2 MB through the API (50 MB through SMTP), and only from TEM's list of allowed file types. |
| One SPF record | Merge TEM's include into the existing record; two SPF records make both fail. |
| Region | TEM runs in Scaleway's Paris region (fr-par). |
Check Scaleway's Transactional Email documentation and pricing for the current figures; they are Scaleway's to change.
Why two providers instead of one?
Several EU senders also receive email, and one provider means one bill and one DPA. If what you need from inbound is a parsed message at a webhook, that may be enough. A pair makes sense when inbound is the core of your product: attachments in your own storage, virus scanning, rules that reject mail before it reaches you, a payload shaped to your schema. Each side is then run by a service built for that side.
How EmailConnect fits
- Threading fields on every plan.
envelope.messageId,inReplyToandreferencesstay in the payload even when envelope data is switched off. Payload reference → - Plus tags as routing keys. One alias serves every conversation; the tag arrives as
message.recipient.tag. Plus addressing → - Processed only in the EU, like TEM, so the whole conversation stays with European providers. Sub-processors →
- Signed deliveries. Verify every webhook before your app sends anything in response. Webhook signing →
Set up the inbound half
Create a support alias, point it at your endpoint and send it a test email. The Free plan is enough to build the whole flow.
Related
Build a privacy-friendly AI support responder
Draft the replies with your own LLM and knowledge base, then send them with this setup.
Using subdomains for email processing
Receive on in.yourdomain.com without touching your main mail.
Sending replies
The short version: what EmailConnect does and doesn't do for outbound mail.
The European tech stack in 2026
The EU providers EmailConnect runs on, Scaleway included, and where I compromised.