Checky sends sign-in codes, invitations, check-in reminders and digests. Every reminder carries one-tap answer buttons (signed links, no sign-in needed) and can be answered by replying with “yes”, “no”, a number or text — in English, German, French or Spanish.
Sending with SMTP (Docker)
Any SMTP provider works: Postmark, Mailgun, Amazon SES, Brevo, Scaleway TEM (EU), or your Microsoft 365 / Google Workspace relay.
EMAIL_FROM=Checky <[email protected]>
SMTP_URL=smtps://user:[email protected]:465
Use port 465 with smtps://, or port 587 with smtp://… (STARTTLS). URL-encode special characters in the
password. Set up SPF, DKIM and DMARC for the EMAIL_FROM domain as your provider describes — otherwise
reminders end up in spam.
Without SMTP_URL the transport is console: emails are only printed to the log (docker compose logs checky),
and the server warns about it at start in production.
On Cloudflare, sending uses Cloudflare Email instead — see Deploy to Cloudflare.
Answering by reply
Reminders are sent with a Reply-To: reply+<token>@EMAIL_REPLY_DOMAIN address. The token is signed with
APP_SECRET and tied to one check-in, so nobody can answer for somebody else. Quoted history, signatures and
sign-offs (“Viele Grüße”, “Sent from my iPhone”) are stripped before the answer is read. Replies are accepted
until seven days after the deadline.
On Docker you need an inbound email provider that receives mail for the reply domain and posts it to Checky:
- Pick a domain for replies, e.g.
reply.example.com, and setEMAIL_REPLY_DOMAIN=reply.example.com. - Set
INBOUND_WEBHOOK_SECRETto a random value (≥ 16 characters). - Point the domain’s MX record to your inbound provider and forward every message to
https://checky.example.com/api/v1/inbound/email?secret=<INBOUND_WEBHOOK_SECRET>(or send the secret in theX-Checky-Inbound-Secretheader):- Mailgun — Receiving → Routes → “Forward” to the URL.
- Postmark — Inbound stream → webhook URL.
- SendGrid — Inbound Parse (raw MIME or parsed both work).
Checky understands Mailgun, Postmark and SendGrid payloads, a generic JSON body (from, to, subject,
text, html) and raw MIME. The response is always 200 for a message it understood, with a result that
says what happened (answered, already answered, unknown token …).
Checking that it works
- Create a check due in a few minutes and assign yourself.
- Wait for the reminder email.
- Reply “yes”. The check-in shows as answered, with “email” as the channel.
If nothing arrives, see Troubleshooting.