Receive email into SQS
A complete inbound-email walkthrough: give an address to a vendor, get a small notice on your SQS queue for every message it receives, and pull the message and its attachments over the API with your own key. The queue carries no mail content. Routing to an HTTPS endpoint instead? It gets the same notice, plus the email's text, HTML and headers. See Receiving over HTTPS.
What you'll build
vendor ──▶ invoices@mail.acme.com ──▶ Emithook MX ──▶ SQS acme-invoices-q (email.received notice)
(SPF/DKIM/DMARC, spam, virus, │
parse, store) ▼
your consumer ──▶ GET /v1/aliases/al_…/messages/evt_…
└▶ GET …/attachments/att_…Why a notice and not the email itself: a message with attachments easily exceeds an SQS message's size limit. The notice is a few KB. Your consumer fetches only what it needs, using its own API key, so it can only read mail that belongs to its org.
You need:
- An API key (console → Settings → API Keys).
writescope to set things up;readis enough for the consumer. - An SQS queue that Emithook can write to. Follow Deliver to Amazon SQS steps 1–4 to create the destination and its queue policy. You'll get a
dst_…id. - For an address on your own domain (
invoices@mail.acme.com): an email domain that is verified and receiving, with itsdom_…id. Without one, use a platform address instead (invoices.<org-slug>@<platform-domain>, no DNS needed).
1. Create the address with its destination
One request creates the address (an alias) and routes it to your queue:
curl -X POST https://api.emithook.com/v1/aliases \
-H "Authorization: Bearer $EMITHOOK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "custom",
"domainId": "dom_01KX9Y3T6R2M8Q4W7Z1B5N0C9D",
"localPart": "invoices",
"destinationIds": ["dst_01KXBRV8HB64MA2H6QM49B2V8F"]
}'| Field | Meaning |
|---|---|
kind | custom (your domain, needs domainId) or platform (shared domain, no domainId). |
domainId | The email domain's id (dom_…), not its name. List them with GET /v1/domains. |
localPart | The part before @. * on a custom domain is a catch-all. |
destinationIds | Destination ids (dst_…), not URLs. |
Optional: allowedSenders (only accept these senders) and maxSizeBytes.
Response 201:
{
"id": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"address": "invoices@mail.acme.com",
"kind": "custom",
"localPart": "invoices",
"domain": "mail.acme.com",
"destinationIds": ["dst_01KXBRV8HB64MA2H6QM49B2V8F"],
"createdAt": "2026-10-16T09:00:12.481Z"
}Keep the id (al_…): it's the alias identifier in every inbox URL.
| Error | Why |
|---|---|
409 conflict | The address already exists in your org. |
400 bad_request | Invalid localPart, unknown or unverified domainId, or missing kind. |
Check your destination id
destinationIds aren't validated when the alias is created. A typo creates an address whose mail goes nowhere. Copy the id from GET /v1/destinations or the console.
To change routing later: PATCH /v1/aliases/al_… with {"destinationIds": [...]}. The address itself can't be changed.
2. Check whether an address is defined
There's no lookup-by-address endpoint. List your aliases and match on address (stored lowercase):
EMAIL="invoices@mail.acme.com"
curl -s "https://api.emithook.com/v1/aliases?limit=100" \
-H "Authorization: Bearer $EMITHOOK_API_KEY" \
| jq --arg e "$(echo "$EMAIL" | tr A-Z a-z)" '.data[] | select(.address == $e)'No output means the address isn't defined in your org. The list is paginated: follow next_cursor if you have more than 100 aliases. Alternatively, just create it and treat 409 as "already exists".
3. What arrives on the queue
Send a test email to the address. Within seconds one SQS message arrives. Its MessageBody is the standard delivery envelope, with metadata.source: "email" and an email.received notice as data.body:
{
"version": 2,
"metadata": {
"event_id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"identifier": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"name": "invoices@mail.acme.com",
"source": "email",
"url": null,
"method": null,
"path": null,
"content_type": "application/json",
"body_encoding": "json",
"size": 1426,
"source_ip": null,
"verification": {
"status": "unverified",
"scheme": "none"
},
"transformed": false,
"captured_at": "2026-10-16T09:12:39.107Z",
"posted_at": "2026-10-16T09:12:41.207Z",
"expanded_json_paths": []
},
"data": {
"body": {
"type": "email.received",
"id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"alias_id": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"message_id": "<CAKd9fP3x7Q@mail.gmail.com>",
"alias": "invoices@mail.acme.com",
"from": "billing@vendor.com",
"to": [
"invoices@mail.acme.com"
],
"cc": [
"ap-team@acme.com"
],
"subject": "Invoice INV-2291 for October",
"date": "2026-10-16T09:12:37.000Z",
"received_at": "2026-10-16T09:12:39.107Z",
"size_bytes": 271667,
"auth": "verified",
"verdicts": {
"spf": "PASS",
"dkim": "PASS",
"dmarc": "PASS",
"spam": "PASS",
"virus": "PASS"
},
"attachments": [
{
"id": "att_01M50GHMJRP15Z4602V9XFVKDA_0",
"name": "INV-2291.pdf",
"size": 182340,
"content_type": "application/pdf",
"content_id": null,
"disposition": "attachment",
"url": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/attachments/att_01M50GHMJRP15Z4602V9XFVKDA_0"
},
{
"id": "att_01M50GHMJRP15Z4602V9XFVKDA_1",
"name": "Screenshot 2026-10-16 at 12.24.09 AM.png",
"size": 79848,
"content_type": "image/png",
"content_id": "ii_mgb3k9x10",
"disposition": "inline",
"url": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/attachments/att_01M50GHMJRP15Z4602V9XFVKDA_1"
}
],
"api": {
"message": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA",
"raw": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/raw"
}
},
"headers": {
"content-type": "application/json"
},
"queries": {}
}
}Notice fields
| Field | Meaning |
|---|---|
type | Always email.received. |
id | The message id (evt_…), the same as metadata.event_id. Use it as your idempotency key. |
alias_id | The alias id (al_…). |
message_id | The email's own RFC Message-ID header. Informational; you don't need it in URLs. |
alias | The address the mail was sent to. |
from | Sender address (plain, no display name). |
to, cc | Lists of plain addresses. cc is [] when there was none. |
subject | Subject line. |
date | The email's own Date: header. |
received_at | When Emithook received it. |
size_bytes | Size of the whole raw message. |
auth, verdicts | verdicts gives each check: PASS, FAIL, GRAY or PROCESSING_FAILED. auth summarises SPF/DKIM/DMARC: failed only when one of them is FAIL or PROCESSING_FAILED, otherwise verified. GRAY (no DKIM signature, or no DMARC policy — dmarc=none) is not a failure. |
attachments[] | id (the download key), name (the exact Unicode filename), size (bytes), content_type, content_id and disposition (below), and a ready-made download url. |
api.message | URL for the full parsed message (text, HTML, all headers). |
api.raw | URL for the original .eml. |
Inline images
A picture pasted into the body (a screenshot, a signature logo) is a file with disposition: "inline" and a content_id. The HTML refers to it as src="cid:<content_id>":
{ "id": "att_01M50GHMJRP15Z4602V9XFVKDA_1", "name": "Screenshot 2026-10-16 at 12.24.09 AM.png", "content_id": "ii_mgb3k9x10", "disposition": "inline", "…": "…" }<img src="cid:ii_mgb3k9x10" alt="Screenshot">To render the body, download each inline file from its url and replace cid:<content_id> in the HTML with wherever you stored it. A regular attachment has disposition: "attachment" and usually content_id: null. Both are null for emails received before 16 Oct 2026, and those older emails have their inline images embedded in the HTML as data: URIs instead.
For email, metadata.verification is always unverified / none: it describes webhook signatures. Read the sender checks from data.body.auth and data.body.verdicts instead.
Receiving over HTTPS
Route the alias to an HTTPS destination and each email arrives as a POST of the same envelope, with the same email.received notice as data.body. HTTPS has no message size limit, so the notice also carries the email's content as three extra keys. Nothing from the queue notice is removed or renamed, so one DTO and one handler serve both:
| Extra key (HTTPS only) | Meaning |
|---|---|
text | The plain-text body, or null. |
html | The HTML body, or null. |
headers | Every header, by lowercase name, as its original text (for example "cc": "\"AP Team\" <ap-team@acme.com>"). |
The full request, for the same email as the queue sample above:
POST /emithook/email HTTP/1.1
Host: api.acme.com
Content-Type: application/json
webhook-id: evt_01M50GHMJRP15Z4602V9XFVKDA
webhook-timestamp: 1792141961
webhook-signature: v1,EAPUv78RVn6zAmgt60WUIW4mhPkQZjLhonQqihqJvC0=
{
"version": 2,
"metadata": {
"event_id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"identifier": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"name": "invoices@mail.acme.com",
"source": "email",
"url": null,
"method": null,
"path": null,
"content_type": "application/json",
"body_encoding": "json",
"size": 2359,
"source_ip": null,
"verification": {
"status": "unverified",
"scheme": "none"
},
"transformed": false,
"captured_at": "2026-10-16T09:12:39.107Z",
"posted_at": "2026-10-16T09:12:41.207Z",
"expanded_json_paths": []
},
"data": {
"body": {
"type": "email.received",
"id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"alias_id": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"message_id": "<CAKd9fP3x7Q@mail.gmail.com>",
"alias": "invoices@mail.acme.com",
"from": "billing@vendor.com",
"to": [
"invoices@mail.acme.com"
],
"cc": [
"ap-team@acme.com"
],
"subject": "Invoice INV-2291 for October",
"date": "2026-10-16T09:12:37.000Z",
"received_at": "2026-10-16T09:12:39.107Z",
"size_bytes": 271667,
"auth": "verified",
"verdicts": {
"spf": "PASS",
"dkim": "PASS",
"dmarc": "PASS",
"spam": "PASS",
"virus": "PASS"
},
"attachments": [
{
"id": "att_01M50GHMJRP15Z4602V9XFVKDA_0",
"name": "INV-2291.pdf",
"size": 182340,
"content_type": "application/pdf",
"content_id": null,
"disposition": "attachment",
"url": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/attachments/att_01M50GHMJRP15Z4602V9XFVKDA_0"
},
{
"id": "att_01M50GHMJRP15Z4602V9XFVKDA_1",
"name": "Screenshot 2026-10-16 at 12.24.09 AM.png",
"size": 79848,
"content_type": "image/png",
"content_id": "ii_mgb3k9x10",
"disposition": "inline",
"url": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/attachments/att_01M50GHMJRP15Z4602V9XFVKDA_1"
}
],
"api": {
"message": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA",
"raw": "https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA/raw"
},
"text": "Hi team,\n\nPlease find attached invoice INV-2291 for October. The portal error is below:\n\n[image: Screenshot]\n\nThanks,\nBilling",
"html": "<p>Hi team,</p><p>Please find attached invoice INV-2291 for October. The portal error is below:</p><p><img src=\"cid:ii_mgb3k9x10\" alt=\"Screenshot\"></p><p>Thanks,<br>Billing</p>",
"headers": {
"received": "from mail-yx1-f52.google.com (mail-yx1-f52.google.com [74.125.224.52]) by inbound-smtp.ap-south-1.amazonaws.com with SMTP id 4kq2v9; Fri, 16 Oct 2026 09:12:38 +0000\nby mail-yx1-f52.google.com with SMTP id 00721157ae682; Fri, 16 Oct 2026 02:12:37 -0700",
"from": "Billing <billing@vendor.com>",
"to": "invoices@mail.acme.com",
"cc": "\"AP Team\" <ap-team@acme.com>",
"subject": "Invoice INV-2291 for October",
"date": "Fri, 16 Oct 2026 09:12:37 +0000",
"message-id": "<CAKd9fP3x7Q@mail.gmail.com>",
"mime-version": "1.0",
"content-type": "multipart/mixed; boundary=\"000000000000b1c2d3e4f5\""
}
},
"headers": {
"content-type": "application/json"
},
"queries": {}
}
}Compared with the queue message, metadata and every data.body key up to api are identical. text, html and headers are the only additions. (metadata.size is the size of data.body, so it's larger here.)
The webhook-* headers are sent when the destination has a signing secret; the signature above was computed with an example secret, so verify against your destination's own whsec_….
- Attachments and the raw
.emlare never pushed. Download them from theurls exactly as a queue consumer does. - The request is signed with Standard Webhooks headers (
webhook-id= the email'sevt_…id,webhook-timestamp,webhook-signature) when the destination has a signing secret. Verify against the raw request bytes. See the delivery envelope. - Reply
2xxquickly, then process in the background. Anything else (or a timeout) is retried with backoff, so make the handler idempotent ondata.body.id.
A NestJS controller that queues the work:
@Post()
@HttpCode(HttpStatus.OK)
async receive(@Body() body: EmithookEnvelopeDto<EmithookEmailBodyInterface>) {
if (body?.version === 2 && body.metadata?.source === 'email' && body.data?.body?.type === 'email.received') {
await this.handleEmithookEmailJob.dispatch(body) // same job the SQS consumer runs
}
return { success: true }
}Type the HTTPS-only keys as optional (text?: string | null; html?: string | null; headers?: Record<string, string>) so the same interface fits the queue notice.
4. Fetch the message and attachments
Call the URLs from the notice with your API key. A read-scoped key works, and it only sees your org's mail.
MSG=https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/messages/evt_01M50GHMJRP15Z4602V9XFVKDA
# The parsed message
curl -s "$MSG" -H "Authorization: Bearer $EMITHOOK_API_KEY"
# One attachment (raw bytes)
curl -s "$MSG/attachments/att_01M50GHMJRP15Z4602V9XFVKDA_0" \
-H "Authorization: Bearer $EMITHOOK_API_KEY" -o INV-2291.pdf
# The original .eml
curl -s "$MSG/raw" -H "Authorization: Bearer $EMITHOOK_API_KEY" -o message.emlGET $MSG response:
{
"id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"message_id": "<CAKd9fP3x7Q@mail.gmail.com>",
"event_id": "evt_01M50GHMJRP15Z4602V9XFVKDA",
"alias": "invoices@mail.acme.com",
"from": "billing@vendor.com",
"to": ["invoices@mail.acme.com"],
"cc": ["ap-team@acme.com"],
"subject": "Invoice INV-2291 for October",
"received_at": "2026-10-16T09:12:39.107Z",
"date": "2026-10-16T09:12:37.000Z",
"status": "accepted",
"auth": "verified",
"text": "Hi team,\nPlease find attached invoice INV-2291 …",
"html": "<p>Hi team,</p><p>Please find attached invoice INV-2291 …</p>",
"size_bytes": 271667,
"headers": {
"from": "Billing <billing@vendor.com>",
"to": "invoices@mail.acme.com",
"cc": "\"AP Team\" <ap-team@acme.com>",
"subject": "Invoice INV-2291 for October",
"message-id": "<CAKd9fP3x7Q@mail.gmail.com>",
"date": "Fri, 16 Oct 2026 09:12:37 +0000",
"content-type": "multipart/mixed; boundary=\"000000000000b1c2d3\""
},
"verdicts": { "spf": "PASS", "dkim": "PASS", "dmarc": "PASS", "spam": "PASS", "virus": "PASS" },
"attachments": [
{ "id": "att_01M50GHMJRP15Z4602V9XFVKDA_0", "name": "INV-2291.pdf", "size": 182340, "content_type": "application/pdf",
"content_id": null, "disposition": "attachment" },
{ "id": "att_01M50GHMJRP15Z4602V9XFVKDA_1", "name": "Screenshot 2026-10-16 at 12.24.09 AM.png", "size": 79848, "content_type": "image/png",
"content_id": "ii_mgb3k9x10", "disposition": "inline" }
]
}headersholds each header's original text, so Cc keeps its display names. Use thetoandcclists for plain addresses; don't split the header strings yourself. A header that appears more than once (eachReceivedhop) is one string with one occurrence per line, in message order.- Attachment downloads return the raw bytes with the attachment's
content-type. Take the filename fromnameon the message.Content-Dispositioncarries an ASCII fallback (filename="…") plus the exact name as RFC 5987filename*=UTF-8''…, so names with non-ASCII characters (like the narrow space in macOS screenshot names) download fine.
| Status | Why |
|---|---|
401 | Missing or invalid API key. |
404 | Not found for this key: wrong id, another org's alias, an attachment id from a different message, or the stored bytes have expired. |
400 | Malformed attachment id. |
5. A consumer
Node.js
With the AWS SDK v3 and Node 18+ fetch:
import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs'
import { writeFile } from 'node:fs/promises'
const sqs = new SQSClient({ region: 'ap-south-1' })
const QueueUrl = process.env.QUEUE_URL!
const auth = { Authorization: `Bearer ${process.env.EMITHOOK_API_KEY}` }
interface EmailAttachment {
id: string; name: string | null; size: number; content_type: string
content_id: string | null; disposition: 'inline' | 'attachment' | null; url?: string
}
interface EmailReceived {
type: 'email.received'
id: string; alias_id: string | null; message_id: string | null; alias: string | null
from: string | null; to: string[]; cc: string[]; subject: string | null
date: string | null; received_at: string | null; size_bytes: number | null
auth: 'verified' | 'failed' | null
verdicts: Record<'spf' | 'dkim' | 'dmarc' | 'spam' | 'virus', string> | null
attachments: EmailAttachment[]
api: { message: string; raw: string } | null
}
async function handle(n: EmailReceived): Promise<void> {
if (await seenBefore(n.id)) return // at-least-once delivery: dedupe on the message id
// Full message only when you need the body or headers
const msg = await (await fetch(n.api!.message, { headers: auth })).json()
for (const a of n.attachments) {
const res = await fetch(a.url!, { headers: auth })
if (!res.ok) throw new Error(`attachment ${a.id}: HTTP ${res.status}`)
await writeFile(`./inbox/${a.id}-${a.name ?? 'attachment'}`, Buffer.from(await res.arrayBuffer()))
}
await saveEmail(n, msg)
await markSeen(n.id)
}
while (true) {
const { Messages = [] } = await sqs.send(
new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 10, WaitTimeSeconds: 20 }),
)
for (const m of Messages) {
const env = JSON.parse(m.Body!)
if (env.version !== 2) throw new Error(`unsupported envelope version ${env.version}`)
if (env.metadata.source === 'email' && env.data.body?.type === 'email.received') {
await handle(env.data.body as EmailReceived)
}
// delete only after it was handled; a throw leaves it on the queue for a retry
await sqs.send(new DeleteMessageCommand({ QueueUrl, ReceiptHandle: m.ReceiptHandle! }))
}
}Python
import json, os, boto3, requests
sqs = boto3.client("sqs", region_name="ap-south-1")
QUEUE_URL = os.environ["QUEUE_URL"]
AUTH = {"Authorization": f"Bearer {os.environ['EMITHOOK_API_KEY']}"}
while True:
resp = sqs.receive_message(QueueUrl=QUEUE_URL, MaxNumberOfMessages=10, WaitTimeSeconds=20)
for m in resp.get("Messages", []):
env = json.loads(m["Body"])
n = env["data"]["body"]
if env["metadata"]["source"] == "email" and n.get("type") == "email.received" and not seen_before(n["id"]):
msg = requests.get(n["api"]["message"], headers=AUTH, timeout=30).json()
for a in n["attachments"]:
r = requests.get(a["url"], headers=AUTH, timeout=60)
r.raise_for_status()
save_attachment(n["id"], a["name"], a["content_type"], r.content)
save_email(n, msg)
mark_seen(n["id"])
sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=m["ReceiptHandle"])6. Re-deliver
One email
From the console (Inbound email → message → Re-deliver) or the API:
curl -X POST https://api.emithook.com/v1/events/evt_01M50GHMJRP15Z4602V9XFVKDA/replay \
-H "Authorization: Bearer $EMITHOOK_API_KEY"
# → 202Every email an alias received in a time range
Pass the alias and a window. Every matching email is sent again to the destinations the alias routes to now, including ones already delivered:
# 1. Preview: how many would be re-sent
curl -X POST https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/replay \
-H "Authorization: Bearer $EMITHOOK_API_KEY" -H "Content-Type: application/json" \
-d '{ "since": "2026-10-15T00:00:00Z", "until": "2026-10-15T23:59:59Z", "dry_run": true }'{
"replay_id": null,
"alias_id": "al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7",
"dry_run": true,
"scanned": 42,
"replayed": 41,
"enqueued": 41,
"skipped": 0,
"excluded": 1,
"destinations": ["dst_01KXBRV8HB64MA2H6QM49B2V8F"],
"next_cursor": null
}# 2. Run it (same body without dry_run) → 202
curl -X POST https://api.emithook.com/v1/aliases/al_01KXC4Q2M7Z5T8N3R6W9Y1B4D7/replay \
-H "Authorization: Bearer $EMITHOOK_API_KEY" -H "Content-Type: application/json" \
-d '{ "since": "2026-10-15T00:00:00Z", "until": "2026-10-15T23:59:59Z" }'
# → { "replay_id": "rpl_ee19dc5fb2be4881851e84f872ec9a93", "replayed": 41, "enqueued": 41, "next_cursor": null, … }| Body field | Meaning |
|---|---|
since, until | Required. ISO-8601 date-times, inclusive, on when Emithook received the email. |
status | Optional: only emails whose delivery is currently delivered, failed or dlq. Use failed or dlq to retry just the ones that didn't land. |
dry_run | true counts without sending (200). Otherwise the emails are queued (202). |
cursor | Continue a large window: each call handles up to 1,000 emails and returns next_cursor while more remain. Repeat with "cursor": "<next_cursor>" until it is null. |
- The alias can be its id (
al_…) or its address, percent-encoded (invoices%40mail.acme.com). excludedcounts quarantined emails in the window. They are never sent to destinations.404means no such alias in your org.409means the alias has no destinations.400means a bad date, orsinceis afteruntil.
Re-sent notices carry the same id and a new metadata.posted_at, and every attempt in the run is stamped with the replay_id. If your consumer dedupes on id, it will drop replays, so make the handler an idempotent upsert if replays must be reprocessed.
7. Troubleshoot
- Delivery status for each message is on the console's Delivery tab: attempts, the latest result and the next retry.
- Nothing on the queue? Check that the alias's
destinationIdscontains your SQS destination, and that the destination shows Reachable (SQS guide, step 4). - Quarantined mail (a failing verdict, e.g. spam) is stored and visible in the console inbox, but isn't routed to destinations.
- The old URL forms still work. You can use the alias address and the RFC Message-ID in place of the ids, percent-encoded (
/v1/aliases/invoices%40mail.acme.com/messages/%3CCAKd9fP3x7Q%40mail.gmail.com%3E), and an attachment's index (/attachments/0) in place of its id. New code should use the ids from the notice. - HTTPS destinations get the same notice plus
text,htmlandheaders(Receiving over HTTPS). Attachment bytes and the raw.emlare never pushed; fetch them from the URLs.