cdn.vapp.vn
← All services

Email Relay

Beta

mail.vappcdn.com

Transactional and mailbox email, without renting someone else's reputation.

Compatible with SMTP · IMAP · JMAP

Usage guide

1 · Your API key, and the call format

Every call is a POST with a bearer token, sent to the origin below. Keep the origin in one config value rather than scattered through your code.

bash# API origin: https://cdn.vapp.vn

curl -sX POST https://cdn.vapp.vn/app/rpc/account/me \
  -H "authorization: Bearer $VAPPCDN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"json":{}}'

# The {"json": …} envelope is required going IN as well as OUT.
# success: {"json": …}
# error:   {"json":{"code":"FORBIDDEN","status":403,"message":"…"}}

2 · Register a sending domain

Use a subdomain, not your apex: it isolates sending reputation from the domain that carries your website and your DNS. We follow the same rule ourselves.

bashcurl -sX POST https://cdn.vapp.vn/app/rpc/mail/addDomain \
  -H "authorization: Bearer $VAPPCDN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"json":{"domain":"send.example.com","brandDomain":"example.com"}}'

# → expectations: { dkimSelector, spfMechanism, dkimRecord }
#   Publish the selector WE return, not one you choose.

3 · Publish three records, then verify

SPF names a HOSTNAME, not an IP — so when our sending infrastructure moves, you change nothing. If you already have an SPF record, ADD the mechanism to it: two SPF records is a permanent error at every receiver.

dnssend.example.com                       TXT  "v=spf1 a:mail.vappcdn.com ~all"
<selector>._domainkey.send.example.com TXT  "v=DKIM1; k=rsa; p=…"
_dmarc.example.com                     TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com"

# then, until status is "active":
POST /app/rpc/mail/checkDomain  {"json":{"domain":"send.example.com"}}

# Per-record status: ok · missing · invalid · unknown
# "unknown" means DNS did not answer us. It is NOT a failure — poll again.
# We check through a public resolver, never our own nameservers.

4 · Mint a submission credential

One credential is one reputation: quota, warmup and suppression are all keyed on it. One credential per sending identity, and never reuse one across products.

bashcurl -sX POST https://cdn.vapp.vn/app/rpc/mail/createCredential \
  -H "authorization: Bearer $VAPPCDN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"json":{"domain":"send.example.com","name":"prod","localPart":"noreply"}}'

# → {"login":"noreply@send.example.com","secret":"…"}
# The secret is returned ONCE. There is no way to read it back.

5 · Send, and read the per-recipient outcome

The result is per recipient, because the outcomes genuinely differ. A caller that reads only the HTTP code records a deferred address as sent.

bashcurl -sX POST https://cdn.vapp.vn/app/rpc/mail/send \
  -H "authorization: Bearer $VAPPCDN_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"json":{"from":"noreply@send.example.com","fromName":"Example",
               "to":["someone@example.org"],
               "subject":"Confirm your email",
               "text":"Confirm here: https://example.com/confirm?t=…"}}'

# → {"messageCount":1,
#    "outcomes":[{"to":"someone@example.org","status":"accepted","code":250}]}
#
#   accepted   we took responsibility for it — NOT the same as delivered
#   deferred   temporary; retry later, nothing is lost
#   refused    permanent; do not retry
#   suppressed never left the building (suppression list, or an unverified domain)
#
# Each recipient gets its own message: we never put several addresses on one
# To:, which would disclose your recipient list to everyone on it.

6 · Receiving — forwarding

Receiving needs MX pointed at us; sending does not. Do not point MX at us while another provider still handles your mail.

bash# catch-all: everything at the domain lands in one mailbox
POST /app/rpc/mail/addForward
  {"json":{"domain":"example.com","kind":"catchall","destEmail":"you@example.net"}}

# one address
POST /app/rpc/mail/addForward
  {"json":{"domain":"example.com","kind":"external",
           "source":"support","destEmail":"team@example.com"}}

# Forwarded mail is re-sent by us, so the envelope sender is rewritten. You do
# not configure that; it is what keeps SPF passing at the final destination.

Endpoints and error codes

Every endpoint is POST /app/rpc/<namespace>/<procedure> — no path parameters, no query strings. Sending to arbitrary recipients opens up once your sending domain reaches active; before that the API is working correctly when it returns suppressed.

nameacceptsnotes
mail/addDomaindomainsRegisters a sending domain and returns the records to publish.
mail/checkDomaindomainsChecks SPF, DKIM and DMARC through a public resolver.
mail/refreshDkimdomainsRe-fetches the DKIM record if the first call had none.
mail/createCredentialsendersMints a submission credential. The secret is shown once.
mail/sendsendSends a message. The result is per recipient.
mail/addForwardreceiveCatch-all or single-address forwarding.
mail/listForwardsreceiveLists the forwarding rules on a domain.
mail/removeForwardreceiveRemoves a forwarding rule.
401 UNAUTHORIZEDerrorNo key, or the key is revoked. Do not retry.
403 FORBIDDENerrorPolicy said no — unverified domain, or a plan limit. Never retryable.
404 NOT_FOUNDerrorDoes not exist, OR is not yours — deliberately not distinguished.
429 TOO_MANY_REQUESTSerrorDeferred, or rate limited. Retry with backoff — this one you should.
503 SERVICE_UNAVAILABLEerrorAn outage on our side. Nothing was sent. Retry.