Skip to the article
Aniq-UI

TicketoryTickets, passes and emails

Tickets, passes and emails

What a buyer receives: a pass per ticket with a signed QR code, the PDF tickets and a calendar file, the emails the API sends and where they go without a mail service, and how staff reissue or void a ticket.

For the Full Stack package

What a ticket is

A ticket is one admission: one seat at a reserved event, or one place at a general-admission event. Its reference is the order's plus a number (TKT-1042-1), and it carries the seat, the holder's name from the order, the ticket type and what was paid for it.

StatusMeaning
ValidOpens the door, once.
Refund pendingSuspended as soon as a refund is asked for, while it waits for the provider, and still after a failed refund until it is retried or restored. The door refuses it.
VoidRefunded or voided. The door refuses it, and its pass shows no QR code.

Passes, PDF tickets and calendar files

  • The order confirmation and the attendee's My tickets show one pass per ticket, with its QR code, the seat, the holder and the ticket type, in English or Arabic.
  • Download tickets opens a PDF with every valid ticket of the order, one per A4 page, drawn in the event's colour with its QR code; a single pass downloads its own PDF. The PDF's text is Latin only, so the Arabic version of a ticket is the web pass.
  • Add to calendar downloads a calendar file for the event, with the venue and a link to the event page.
  • The download links are signed and work for 30 minutes; the page makes fresh ones each time it opens. They are built from the API's PUBLIC_API_URL, so set it to the API's public address.

QR codes

  • The QR code holds a code that starts with TKY., a signature of the ticket's reference made with TICKET_SECRET (the API's JWT_SECRET when that is unset). It contains nothing personal.
  • Only a fingerprint of each code is stored, so the database never holds a usable ticket.
  • Set `TICKET_SECRET` once, before you sell. Changing it later makes every QR code already issued stop working at the door.
Terminal
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

The emails

EmailSent when
Your ticketsAn order is paid or a free order is confirmed, and again after a reissue or a resend
Verification codeAn attendee signs up or changes their email
Password resetAn attendee asks for a new password
RefundThe provider confirms a refund, or does not, in which case the buyer is told the team is checking it
Event cancelledAn event is cancelled, to every paid order
Event reminderAbout a day before the event, to attendees who keep reminders on in their account (below)
  • Set RESEND_API_KEY and MAIL_FROM (for example Ticketory <tickets@your-domain.com>) in the API's .env to send them through Resend. A reply to a ticket email goes to the workspace's support email. Every email about an order (tickets, refunds, the cancellation notice and the reminder) is written in the language the order was placed in, English or Arabic, with the event's title in that language; a resend asked for from the website follows the page's language. The verification and reset emails follow the page the attendee used.
  • Without them, every email is written to the API's log instead of being sent, which is where a verification code is read on a fresh install (with Docker: docker compose logs api). The confirmation then says the tickets are ready to download.
  • A failed send is retried up to six times, with a growing wait. The confirmation shows the email's state and offers Resend email, once a minute.
  • The dashboard's Notifications switches change what the team is told; they never stop an email reaching a buyer.

Event reminders

  • An attendee with an account who keeps Event reminders on in their profile (on by default) gets one email per event, about 24 hours before the first session they hold valid tickets for, listing those tickets with their seats and a link to My tickets.
  • The API looks for due reminders a minute after it starts and every 15 minutes after that. Each one is recorded in the event_reminders table before it is sent, so none goes out twice, even after a restart or with two copies of the API. A failed send is tried up to three times while the event is still ahead.
  • Refunded, suspended and void tickets, unpaid orders and cancelled or draft events get none, and nor does an order paid after the reminder time, since its tickets have just been sent.
  • EVENT_REMINDER_HOURS in the API's .env changes the lead time (at most 168, a week), and 0 turns reminders off. Without a mail provider each reminder is written to the API's log, like every other email.
  • The News and offers switch in the profile is only saved as the attendee's choice: the template sends no news or offer emails.

Reissue and void

  • Reissue, on a ticket in the order's page, gives the ticket a new QR code: the old one stops working at the door at once, and the tickets are emailed again with the new code. Only a valid ticket can be reissued.
  • Void is for a ticket nobody paid for, such as a free one. Its seat or place goes back on sale at once, unless its holder was already admitted. A paid ticket is refunded instead, which keeps the money and the admission in step.
  • Both need tickets.manage, which the seeded Owner and Finance roles hold, and both are written to the order's timeline.

The routes behind them

  • GET /api/orders/:reference/receipt answers the order, its tickets with a QR image while the order is paid, the email's state and signed downloads. It needs the order's access token or the attendee's token.
  • GET /api/orders/:reference/tickets.pdf, GET /api/orders/:reference/calendar.ics and GET /api/tickets/:reference/pdf take a signed link (?exp=&sig=); a wrong or expired one is a 404.
  • POST /api/orders/:reference/resend-email queues the email again; sooner than a minute is a 429 COOLDOWN.
  • POST /api/admin/tickets/:reference/reissue and /void need tickets.manage. Voiding a paid ticket is a 409 USE_REFUND.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.