Email a file as attachment
POST /notify/email-attachment takes a file you've just generated — an invoice, a receipt, a signed contract — and delivers it as an attachment on a message that goes out from your own domain. It's built for the exact moment a document exists only because your system produced it seconds ago and now has to reach someone's inbox.
The gap between generating a file and delivering it
Plenty of systems can render a PDF invoice or export a CSV report in milliseconds; far fewer make it trivial to then get that file into an inbox reliably. This endpoint closes that specific gap: you already did the hard part of producing the document, and this call handles the last step — packaging it as an attachment and delivering it — without you having to manage SMTP connections or MIME encoding by hand.
What the request looks like
Alongside the usual recipient, subject and body, you include the file content and its name; the API encodes it correctly for email transport, queues the send and returns a task_id immediately. The actual delivery happens moments later on our global edge, and the outcome — accepted, delivered, bounced, failed — comes back through a signed webhook or a signed link valid for 24 hours, so your system knows for certain the invoice reached its destination and not just that you asked us to send it.
Why attachments are still the safest default
Sending a document as a link instead of an attachment feels modern, but it introduces a dependency: the link has to stay valid, the recipient has to trust clicking it, and some corporate filters flag unfamiliar links more aggressively than a plain attachment. For anything a person needs to keep — a receipt for their records, a signed agreement — an attachment that lands directly in the message is still the format people expect and archive without a second thought.
Where size and format expectations come in
Attachments work best for the documents email was designed to carry: PDFs, images, small spreadsheets — the kind of file a person opens once and files away. Very large binaries strain every mail provider's limits regardless of the sending API, so the practical pattern for bulky exports is generating them as a file and referencing them by link, while this endpoint stays focused on the invoice-sized attachment it does well.
Slotting into a document pipeline
Because the call is asynchronous and retried automatically up to three times before a clear error is returned, it drops cleanly into a pipeline where a PDF generator produces the file and this endpoint ships it in the same request-response cycle, with no charge for a failed attempt.
What you can do with it
Invoices after checkout
The moment your billing system renders a PDF invoice, this endpoint emails it directly to the customer from your own domain.
Signed contracts and agreements
Once a document workflow finalizes a signed PDF, it's delivered as an attachment so the signer has a permanent copy in their inbox.
Weekly report exports
An internal reporting job generates a small CSV or PDF summary and emails it to a stakeholder list without anyone opening a dashboard.
Receipts for one-off payments
A payment confirmation triggers a receipt PDF that's attached and sent the moment the transaction clears.
FAQ
What file types can I attach?
The endpoint is built for the documents email attachments were meant to carry — PDFs, images, small spreadsheets and similar formats generated on the fly.
Is there a size limit on attachments?
Yes, practical limits apply as with any email attachment; very large files should be delivered as a link rather than attached directly.
How much does it cost?
$0.002 per request plus $0.0035 per email sent — the same pricing as a standard transactional email, with no extra fee for the attachment itself.
Is there a free way to test this?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
How do I know the attachment was actually delivered?
The signed webhook or signed link reports the delivery event — accepted, delivered, bounced or failed — so you're not left guessing.
Can I send the same file to multiple recipients in one call?
Each call targets a single send; for multiple recipients, issue one request per recipient so delivery and retries are tracked independently.
What happens if the attachment fails to send?
The task retries automatically up to three times, and a failed task is never charged — you only get billed for a delivery attempt that actually goes out.
Why not just send a download link instead?
Links depend on staying valid and being trusted enough to click; an attachment lands directly in the message, which is still what people expect for receipts and signed documents.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/notify/email-attachment \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/notify/email-attachment", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/notify/email-attachment",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/notify/email-attachment", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/notify/email-attachment", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "notify.email_attachment",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |