Skip to content

Version: Arctyk ITSM v0.6.0+ Last Updated: January 2026

Email Integration

Arctyk ITSM sends email notifications for ticket events and supports SMTP configuration for production deployments.


Overview

Email Features

  • ✅ Ticket creation notifications
  • ✅ Ticket assignment notifications
  • ✅ Status change notifications
  • ✅ Comment notifications
  • ✅ Overdue ticket alerts
  • 🔄 Email-triggered ticket creation (planned)

Current Status

  • Version: v0.6.0+
  • Backend: Django email framework
  • Queue: Celery async task processing
  • Default: Console backend (prints to stdout in development)

Configuration

Development (Console Backend)

By default, Arctyk ITSM prints emails to console output:

# src/config/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

Emails appear in your Django console:

[29/Nov/2025 10:00:00] "POST /tickets/" - New ticket created
...
Subject: New Ticket: Unable to login
From: noreply@arctyk.example.com
To: requester@example.com

Email body content...

Production (SMTP Backend)

Configure SMTP for sending real emails:

# src/config/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-app-password'
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'arctyk@your-domain.com'

Environment Variables

Set via .env:

# SMTP Configuration
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
EMAIL_USE_TLS=True
DEFAULT_FROM_EMAIL=arctyk@your-domain.com

# Alternative: SendGrid
EMAIL_BACKEND=sendgrid_backend.SendgridBackend
SENDGRID_API_KEY=your-sendgrid-api-key

Email Events

Ticket Created

Sent to:

  • Ticket requester
  • Ticket assigned user
  • Project team (if configured)

Example Email:

Subject: New Ticket: Unable to login (TKT-001)
From: noreply@arctyk.example.com
To: user@example.com

Hello John Doe,

A new ticket has been created:

Title: Unable to login
Ticket Number: TKT-001
Priority: High
Description: Users cannot access the system

View Ticket: https://arctyk.example.com/tickets/1/

---
Arctyk ITSM

Ticket Assigned

Sent to newly assigned user.

Subject: Ticket Assigned: {title} (TKT-{number})

Status Changed

Sent to:

  • Requester (if public status)
  • Assigned user
  • Project team

Subject: Ticket Status Changed: {title} → {new_status}

Comment Added

Sent to:

  • Ticket requester
  • Ticket assigned user
  • Other commenters (if public comment)

Subject: New Comment on Ticket: {title} (TKT-{number})

Note: Internal comments are not emailed.

Overdue Alert

Sent to assigned user for tickets past due date.

Subject: Overdue Ticket: {title} (TKT-{number})


Email Implementation

Celery Task

Emails are sent asynchronously via Celery:

# src/tickets/tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_ticket_notification(ticket_id, event_type):
    """Send email notification for ticket event."""
    ticket = Ticket.objects.get(pk=ticket_id)

    if event_type == 'created':
        recipients = [ticket.requester.email]
        subject = f'New Ticket: {ticket.title} ({ticket.ticket_number})'
        message = render_email_template('ticket_created.html', {'ticket': ticket})

    elif event_type == 'assigned':
        recipients = [ticket.assigned_user.email]
        subject = f'Ticket Assigned: {ticket.title}'
        message = render_email_template('ticket_assigned.html', {'ticket': ticket})

    send_mail(
        subject=subject,
        message=message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=recipients,
        html_message=message,
    )

Signal-Based Triggers

Emails are triggered by Django signals:

# src/tickets/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .tasks import send_ticket_notification

@receiver(post_save, sender=Ticket)
def ticket_created(sender, instance, created, **kwargs):
    """Send email when ticket is created."""
    if created:
        send_ticket_notification.delay(instance.id, 'created')

@receiver(post_save, sender=Ticket)
def ticket_status_changed(sender, instance, **kwargs):
    """Send email when ticket status changes."""
    old = Ticket.objects.get(pk=instance.pk)
    if old.status != instance.status:
        send_ticket_notification.delay(instance.id, 'status_changed')

Email Templates

Email templates are HTML files in templates/emails/:

templates/emails/
├── base.html              # Base email template
├── ticket_created.html
├── ticket_assigned.html
├── status_changed.html
├── comment_added.html
└── overdue_alert.html

Template Example

{# templates/emails/ticket_created.html #}
{% extends "emails/base.html" %}

{% block content %}
<h2>New Ticket Created</h2>

<p>Hello {{ ticket.requester.first_name }},</p>

<p>A new ticket has been created in Arctyk ITSM:</p>

<table>
    <tr>
        <td><strong>Title:</strong></td>
        <td>{{ ticket.title }}</td>
    </tr>
    <tr>
        <td><strong>Ticket Number:</strong></td>
        <td>{{ ticket.ticket_number }}</td>
    </tr>
    <tr>
        <td><strong>Priority:</strong></td>
        <td>{{ ticket.get_priority_display }}</td>
    </tr>
</table>

<p>
    <a href="{{ site_url }}/tickets/{{ ticket.id }}/">View Ticket</a>
</p>

{% endblock %}

Testing Email

Console Backend

Emails print to console in development:

python manage.py runserver

When a ticket is created, emails appear in console output.

Send Test Email

Use Django shell:

python manage.py shell

>>> from django.core.mail import send_mail
>>> send_mail(
...     'Test Email',
...     'This is a test',
...     'from@example.com',
...     ['to@example.com'],
... )
1

Email Logging

Monitor email sending:

# See all email-related logs
tail -f logs/email.log

Troubleshooting

Emails Not Sending

Check Configuration:

python manage.py shell

>>> from django.conf import settings
>>> settings.EMAIL_BACKEND
'django.core.mail.backends.console.EmailBackend'
>>> settings.EMAIL_HOST
'smtp.gmail.com'

Check Celery:

# Verify Celery worker is running
celery -A config worker --loglevel=info

# Monitor task queue
python manage.py shell
>>> from django.core.mail.outbox import outbox
>>> len(outbox)  # Should see failed tasks

SMTP Connection Error

SMTPAuthenticationError: (535, b'5.7.8 Username and password not accepted')

Solutions:

  1. Verify credentials are correct
  2. For Gmail: Use "App Password", not your regular password
  3. Check firewall allows outbound SMTP connections
  4. Verify EMAIL_USE_TLS=True for port 587

Rate Limiting

Email providers often rate-limit sending:

  • Gmail: 500 emails per 24 hours
  • SendGrid: Based on plan
  • AWS SES: Sandbox mode: 1 email/second

Solution: Space out emails or use a transactional email service.


Best Practices

1. Use Task Queue

Always send emails async via Celery:

# ✅ Good
send_email_task.delay(user_id)

# ❌ Bad
send_mail(...)  # Blocks request

2. Handle Bounces

Track email bounces to update user email addresses:

def handle_bounce(email):
    """Mark email as bounced."""
    user = User.objects.get(email=email)
    user.email_bounced = True
    user.save()

3. Unsubscribe Option

Always include unsubscribe link:

<p>
  <a href="{{ unsubscribe_url }}">Unsubscribe from notifications</a>
</p>

4. Use Reply-To

Set reply-to address for user responses:

send_mail(
    subject='...',
    message='...',
    from_email=settings.DEFAULT_FROM_EMAIL,
    recipient_list=[...],
    reply_to=['support@example.com'],  # Allow user replies
)

5. Monitor Deliverability

Use email service provider dashboard to monitor:

  • Delivery rates
  • Open rates
  • Click rates
  • Bounce rates

Future Enhancements

Planned Features (v0.7.0+)

  • Email-triggered ticket creation
  • Inline reply handling
  • Email signature preservation
  • Attachment handling
  • User email preferences
  • Email digest/summary
  • Multi-language email templates

Integration Examples

# Parse incoming email and create ticket
def handle_incoming_email(email_message):
    """Create ticket from incoming email."""
    ticket = Ticket.objects.create(
        title=email_message.subject,
        description=email_message.body,
        requester=email_message.sender,
    )
    return ticket