Developer documentation

Send email from your own domain, with one key.

A REST API and an SMTP endpoint over your own DKIM-signed sending domains. No third-party ESP in the middle — you are the provider.

POST  https://lkpfeqrelvziltfwpuxi.supabase.co/functions/v1/mailer

Quickstart

Install the SDK, drop in a key, send. Every request is a POST with a JSON body and a Bearer key — nothing else.

Node SDK

bash
npm i @gofarther/mail
typescript
import { Gofarther } from '@gofarther/mail';

const gf = new Gofarther(process.env.GOFARTHER_API_KEY!);

const { id } = await gf.emails.send({
  from: 'you@yourdomain.com',     // a verified domain
  to: 'someone@example.com',
  subject: 'Hello',
  html: '<p>Hi there 👋</p>',
});

curl

bash
curl https://lkpfeqrelvziltfwpuxi.supabase.co/functions/v1/mailer \
  -H "Authorization: Bearer gf_live_..." \
  -H "Content-Type: application/json" \
  -d '{"action":"send","from":"you@yourdomain.com","to":"someone@example.com","subject":"Hello","html":"<p>Hi there</p>"}'
# → { "id": "<...>" }

API keys

Create keys in the app under Settings → API keys. The full key is shown once at creation — only a hash is stored, so it can't be shown again. Keys carry two properties:

PropertyValuesWhat it means
scopesend · fullsend sends email and reads back your own sending activity. full does all that plus manages domains via the API. Both see only your own account's data.
modelive · testgf_live_… really sends. gf_test_… validates the request and returns a test_… id without sending — wire up your integration before you've even verified a domain.

Send-only keys can never mint or list keys — key management is dashboard-only. Up to 25 active keys.

Sending

action: "send". Send from a domain you've verified; the mail server DKIM-signs it so SPF, DKIM and DMARC all align.

FieldTypeNotes
fromstringrequired. "Name <you@domain>" or "you@domain". Domain must be verified.
tostringrequired. Recipient.
subjectstringrequired.
htmlstringHTML body — provide html and/or text.
textstringPlain-text body.
cc, bccstring · string[]Comma-string or array of addresses.
reply_tostringReply-To address.
attachmentsobject[]{ filename, content (base64), content_type? }. 10 MB total, 20 max.
idempotency_keystringRetries with the same key return the original result.

With an attachment

typescript
await gf.emails.send({
  from: 'billing@yourdomain.com',
  to: 'customer@example.com',
  subject: 'Your invoice',
  html: '<p>Attached.</p>',
  attachments: [{ filename: 'invoice.pdf', content: base64Pdf, content_type: 'application/pdf' }],
});

Success → { "id": "<message-id>" }. Test keys → { "id": "test_…", "test": true }.

Activity any key

See what you've sent and how it landed. Any key — send or full — can read back the emails it sent on your account: recipient, subject, and each one's delivery / bounce status. You only ever see your own data.

typescript
const emails = await gf.emails.list();          // last 100, newest first
const one = await gf.emails.get(emails[0].id);  // full detail + body
console.log(one.status);   // sent | delivered | bounced | complained | failed

action: "messages"{ "messages": [...] }. Each row:

FieldNotes
idThe email's id — pass to message / gf.emails.get.
to_email, from_emailRecipient / sender.
subject
statussent, delivered, bounced, complained, or failed.
errorFailure reason when failed / bounced.
provider_msg_idMail-server message id — maps to webhook / bounce events.
created_at, sent_at, delivered_atTimestamps (nullable until they happen).

action: "message" (body { "id": "<uuid>" }) → { "message": {...} } — the same fields plus body_html and body_text. 404 if the id isn't one of your emails.

bash
curl https://lkpfeqrelvziltfwpuxi.supabase.co/functions/v1/mailer \
  -H "Authorization: Bearer gf_live_..." \
  -H "Content-Type: application/json" \
  -d '{"action":"messages"}'

Domains full scope

Add and verify sending domains programmatically with a full-scope key. Send-only keys get a 403 here.

typescript
const gf = new Gofarther('gf_live_FULL_SCOPE_KEY');

const setup = await gf.domains.add('acme.com');  // → DNS records to publish
// publish setup.records at your DNS host, then:
await gf.domains.verify('acme.com');
await gf.domains.list();
await gf.domains.remove('acme.com');

Actions: domain_add, domain_records, domain_verify, domain_list, domain_remove.

SMTP

No SDK, or a system that only speaks SMTP (like Supabase Auth's custom SMTP)? Point any client at the endpoint — the password is your API key.

smtp
Host      smtp.gofarther.dev
Port      465          # implicit TLS
Username  gofarther
Password  gf_live_...
From      you@yourverifieddomain.com

The SMTP endpoint carries subject / from / to / html / text — ideal for sign-in codes and simple transactional mail. Attachments and cc/bcc go over the HTTP API.

Webhooks

Register endpoints in the app under Webhooks. Each event is signed so you can verify it came from us: header sendra-signature: v1=<hmac>, an HMAC-SHA256 of <timestamp>.<body> using your endpoint secret.

EventFires when
sentthe message was accepted for delivery
deliveredthe receiving server accepted it
bounceddelivery failed permanently
complainedthe recipient marked it as spam
domain.created / updated / deleteda sending domain changed state

Errors

Failures return { "error": "…" } with an HTTP status. The SDK throws a GofartherError carrying .status and .message.

StatusMeaning
400bad input — missing field, or the From domain isn't verified
401bad or revoked key
403the key's scope doesn't allow that action
404that From domain isn't on your account
409the recipient is suppressed (unsubscribed / bounced / complained)
413attachments exceed the size limit
429daily sending limit reached